Merge pull request #1337 from mishaker/wallet

Wallet data structure added. it is initialized on boot then updated right after any trade happens on the exchange.
This commit is contained in:
Matthias 2018-11-22 06:00:49 +01:00 committed by GitHub
commit 8e62fc1c03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 147 additions and 1 deletions

View File

@ -17,6 +17,7 @@ from cachetools import TTLCache, cached
from freqtrade import (DependencyException, OperationalException, from freqtrade import (DependencyException, OperationalException,
TemporaryError, __version__, constants, persistence) TemporaryError, __version__, constants, persistence)
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.wallets import Wallets
from freqtrade.edge import Edge from freqtrade.edge import Edge
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.rpc import RPCManager, RPCMessageType
@ -56,6 +57,7 @@ class FreqtradeBot(object):
self.rpc: RPCManager = RPCManager(self) self.rpc: RPCManager = RPCManager(self)
self.persistence = None self.persistence = None
self.exchange = Exchange(self.config) self.exchange = Exchange(self.config)
self.wallets = Wallets(self.exchange)
# Initializing Edge only if enabled # Initializing Edge only if enabled
self.edge = Edge(self.config, self.exchange, self.strategy) if \ self.edge = Edge(self.config, self.exchange, self.strategy) if \
@ -335,7 +337,9 @@ class FreqtradeBot(object):
else: else:
stake_amount = self.config['stake_amount'] stake_amount = self.config['stake_amount']
# TODO: should come from the wallet
avaliable_amount = self.exchange.get_balance(self.config['stake_currency']) avaliable_amount = self.exchange.get_balance(self.config['stake_currency'])
# avaliable_amount = self.wallets.wallets[self.config['stake_currency']].free
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all()) open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all())
@ -503,6 +507,10 @@ class FreqtradeBot(object):
) )
Trade.session.add(trade) Trade.session.add(trade)
Trade.session.flush() Trade.session.flush()
# Updating wallets
self.wallets.update()
return True return True
def process_maybe_execute_buy(self) -> bool: def process_maybe_execute_buy(self) -> bool:
@ -547,7 +555,14 @@ class FreqtradeBot(object):
if trade.is_open and trade.open_order_id is None: if trade.is_open and trade.open_order_id is None:
# Check if we can sell our current pair # Check if we can sell our current pair
return self.handle_trade(trade) result = self.handle_trade(trade)
# Updating wallets if any trade occured
if result:
self.wallets.update()
return result
except DependencyException as exception: except DependencyException as exception:
logger.warning('Unable to sell trade: %s', exception) logger.warning('Unable to sell trade: %s', exception)
return False return False
@ -686,14 +701,17 @@ class FreqtradeBot(object):
# Check if trade is still actually open # Check if trade is still actually open
if int(order['remaining']) == 0: if int(order['remaining']) == 0:
self.wallets.update()
continue continue
# Check if trade is still actually open # Check if trade is still actually open
if order['status'] == 'open': if order['status'] == 'open':
if order['side'] == 'buy' and ordertime < buy_timeoutthreashold: if order['side'] == 'buy' and ordertime < buy_timeoutthreashold:
self.handle_timedout_limit_buy(trade, order) self.handle_timedout_limit_buy(trade, order)
self.wallets.update()
elif order['side'] == 'sell' and ordertime < sell_timeoutthreashold: elif order['side'] == 'sell' and ordertime < sell_timeoutthreashold:
self.handle_timedout_limit_sell(trade, order) self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
# FIX: 20180110, why is cancel.order unconditionally here, whereas # FIX: 20180110, why is cancel.order unconditionally here, whereas
# it is conditionally called in the # it is conditionally called in the

View File

@ -0,0 +1,84 @@
# pragma pylint: disable=missing-docstring
from freqtrade.tests.conftest import get_patched_freqtradebot
from unittest.mock import MagicMock
def test_sync_wallet_at_boot(mocker, default_conf):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value={
"BNT": {
"free": 1.0,
"used": 2.0,
"total": 3.0
},
"GAS": {
"free": 0.260739,
"used": 0.0,
"total": 0.260739
},
})
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets.wallets) == 2
assert freqtrade.wallets.wallets['BNT'].free == 1.0
assert freqtrade.wallets.wallets['BNT'].used == 2.0
assert freqtrade.wallets.wallets['BNT'].total == 3.0
assert freqtrade.wallets.wallets['GAS'].free == 0.260739
assert freqtrade.wallets.wallets['GAS'].used == 0.0
assert freqtrade.wallets.wallets['GAS'].total == 0.260739
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value={
"BNT": {
"free": 1.2,
"used": 1.9,
"total": 3.5
},
"GAS": {
"free": 0.270739,
"used": 0.1,
"total": 0.260439
},
})
)
freqtrade.wallets.update()
assert len(freqtrade.wallets.wallets) == 2
assert freqtrade.wallets.wallets['BNT'].free == 1.2
assert freqtrade.wallets.wallets['BNT'].used == 1.9
assert freqtrade.wallets.wallets['BNT'].total == 3.5
assert freqtrade.wallets.wallets['GAS'].free == 0.270739
assert freqtrade.wallets.wallets['GAS'].used == 0.1
assert freqtrade.wallets.wallets['GAS'].total == 0.260439
def test_sync_wallet_missing_data(mocker, default_conf):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value={
"BNT": {
"free": 1.0,
"used": 2.0,
"total": 3.0
},
"GAS": {
"free": 0.260739,
"total": 0.260739
},
})
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets.wallets) == 2
assert freqtrade.wallets.wallets['BNT'].free == 1.0
assert freqtrade.wallets.wallets['BNT'].used == 2.0
assert freqtrade.wallets.wallets['BNT'].total == 3.0
assert freqtrade.wallets.wallets['GAS'].free == 0.260739
assert freqtrade.wallets.wallets['GAS'].used is None
assert freqtrade.wallets.wallets['GAS'].total == 0.260739

44
freqtrade/wallets.py Normal file
View File

@ -0,0 +1,44 @@
# pragma pylint: disable=W0603
""" Wallet """
import logging
from typing import Dict, Any, NamedTuple
from collections import namedtuple
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Wallet(NamedTuple):
exchange: str
currency: str
free: float = 0
used: float = 0
total: float = 0
class Wallets(object):
# wallet data structure
wallet = namedtuple(
'wallet',
['exchange', 'currency', 'free', 'used', 'total']
)
def __init__(self, exchange: Exchange) -> None:
self.exchange = exchange
self.wallets: Dict[str, Any] = {}
self.update()
def update(self) -> None:
balances = self.exchange.get_balances()
for currency in balances:
self.wallets[currency] = Wallet(
self.exchange.id,
currency,
balances[currency].get('free', None),
balances[currency].get('used', None),
balances[currency].get('total', None)
)
logger.info('Wallets synced ...')