updateded freqtradebot to include some margin accomadations
This commit is contained in:
parent
ec1a8a8f69
commit
13c8ee9371
@ -67,6 +67,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
|
init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
|
||||||
|
|
||||||
|
# TODO-mg: Do anything with this?
|
||||||
self.wallets = Wallets(self.config, self.exchange)
|
self.wallets = Wallets(self.config, self.exchange)
|
||||||
|
|
||||||
PairLocks.timeframe = self.config['timeframe']
|
PairLocks.timeframe = self.config['timeframe']
|
||||||
@ -78,6 +79,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# so anything in the Freqtradebot instance should be ready (initialized), including
|
# so anything in the Freqtradebot instance should be ready (initialized), including
|
||||||
# the initial state of the bot.
|
# the initial state of the bot.
|
||||||
# Keep this at the end of this initialization method.
|
# Keep this at the end of this initialization method.
|
||||||
|
# TODO-mg: Do I need to consider the rpc, pairlists or dataprovider?
|
||||||
self.rpc: RPCManager = RPCManager(self)
|
self.rpc: RPCManager = RPCManager(self)
|
||||||
|
|
||||||
self.pairlists = PairListManager(self.exchange, self.config)
|
self.pairlists = PairListManager(self.exchange, self.config)
|
||||||
@ -100,6 +102,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
|
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
|
||||||
|
|
||||||
# Protect sell-logic from forcesell and vice versa
|
# Protect sell-logic from forcesell and vice versa
|
||||||
|
# TODO-mg: update to _close_lock
|
||||||
self._sell_lock = Lock()
|
self._sell_lock = Lock()
|
||||||
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
||||||
|
|
||||||
@ -174,13 +177,15 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
self.strategy.analyze(self.active_pair_whitelist)
|
self.strategy.analyze(self.active_pair_whitelist)
|
||||||
|
|
||||||
|
# TODO-mg: update to _close_lock
|
||||||
with self._sell_lock:
|
with self._sell_lock:
|
||||||
# Check and handle any timed out open orders
|
# Check and handle any timed out open orders
|
||||||
self.check_handle_timedout()
|
self.check_handle_timedout()
|
||||||
|
|
||||||
# Protect from collisions with forcesell.
|
# Protect from collisions with forcesell(#TODO-mg: update to forceclose).
|
||||||
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
||||||
# while selling is in process, since telegram messages arrive in an different thread.
|
# while closing is in process, since telegram messages arrive in an different thread.
|
||||||
|
# TODO-mg: update to _close_lock
|
||||||
with self._sell_lock:
|
with self._sell_lock:
|
||||||
trades = Trade.get_open_trades()
|
trades = Trade.get_open_trades()
|
||||||
# First process current opened trades (positions)
|
# First process current opened trades (positions)
|
||||||
@ -258,6 +263,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.info(f"Updating {len(orders)} open orders.")
|
logger.info(f"Updating {len(orders)} open orders.")
|
||||||
for order in orders:
|
for order in orders:
|
||||||
try:
|
try:
|
||||||
|
# TODO-mg: How to consider borrow orders?
|
||||||
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
|
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
|
||||||
order.ft_order_side == 'stoploss')
|
order.ft_order_side == 'stoploss')
|
||||||
|
|
||||||
@ -278,49 +284,56 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
trades: List[Trade] = Trade.get_closed_trades_without_assigned_fees()
|
trades: List[Trade] = Trade.get_closed_trades_without_assigned_fees()
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
|
if not trade.is_open and not trade.fee_updated(trade.exit_side):
|
||||||
if not trade.is_open and not trade.fee_updated('sell'):
|
|
||||||
# Get sell fee
|
# Get sell fee
|
||||||
order = trade.select_order('sell', False)
|
order = trade.select_order(trade.exit_side, False)
|
||||||
if order:
|
if order:
|
||||||
logger.info(f"Updating sell-fee on trade {trade} for order {order.order_id}.")
|
logger.info(
|
||||||
|
f"Updating {trade.exit_side}-fee on trade {trade}"
|
||||||
|
f"for order {order.order_id}."
|
||||||
|
)
|
||||||
self.update_trade_state(trade, order.order_id,
|
self.update_trade_state(trade, order.order_id,
|
||||||
stoploss_order=order.ft_order_side == 'stoploss')
|
stoploss_order=order.ft_order_side == 'stoploss')
|
||||||
|
|
||||||
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
|
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
if trade.is_open and not trade.fee_updated('buy'):
|
if trade.is_open and not trade.fee_updated(trade.enter_side):
|
||||||
order = trade.select_order('buy', False)
|
order = trade.select_order(trade.enter_side, False)
|
||||||
if order:
|
if order:
|
||||||
logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.")
|
logger.info(
|
||||||
|
f"Updating {trade.enter_side}-fee on trade {trade}"
|
||||||
|
f"for order {order.order_id}."
|
||||||
|
)
|
||||||
self.update_trade_state(trade, order.order_id)
|
self.update_trade_state(trade, order.order_id)
|
||||||
|
|
||||||
def handle_insufficient_funds(self, trade: Trade):
|
def handle_insufficient_funds(self, trade: Trade):
|
||||||
"""
|
"""
|
||||||
Determine if we ever opened a sell order for this trade.
|
Determine if we ever opened a exiting order for this trade.
|
||||||
If not, try update buy fees - otherwise "refind" the open order we obviously lost.
|
If not, try update entering fees - otherwise "refind" the open order we obviously lost.
|
||||||
"""
|
"""
|
||||||
sell_order = trade.select_order('sell', None)
|
exit_order = trade.select_order(trade.exit_side, None)
|
||||||
if sell_order:
|
if exit_order:
|
||||||
self.refind_lost_order(trade)
|
self.refind_lost_order(trade)
|
||||||
else:
|
else:
|
||||||
self.reupdate_buy_order_fees(trade)
|
self.reupdate_enter_order_fees(trade)
|
||||||
|
|
||||||
def reupdate_buy_order_fees(self, trade: Trade):
|
def reupdate_enter_order_fees(self, trade: Trade):
|
||||||
"""
|
"""
|
||||||
Get buy order from database, and try to reupdate.
|
Get buy order from database, and try to reupdate.
|
||||||
Handles trades where the initial fee-update did not work.
|
Handles trades where the initial fee-update did not work.
|
||||||
"""
|
"""
|
||||||
logger.info(f"Trying to reupdate buy fees for {trade}")
|
|
||||||
order = trade.select_order('buy', False)
|
logger.info(f"Trying to reupdate {trade.enter_side} fees for {trade}")
|
||||||
|
order = trade.select_order(trade.enter_side, False)
|
||||||
if order:
|
if order:
|
||||||
logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.")
|
logger.info(
|
||||||
|
f"Updating {trade.enter_side}-fee on trade {trade} for order {order.order_id}.")
|
||||||
self.update_trade_state(trade, order.order_id)
|
self.update_trade_state(trade, order.order_id)
|
||||||
|
|
||||||
def refind_lost_order(self, trade):
|
def refind_lost_order(self, trade):
|
||||||
"""
|
"""
|
||||||
Try refinding a lost trade.
|
Try refinding a lost trade.
|
||||||
Only used when InsufficientFunds appears on sell orders (stoploss or sell).
|
Only used when InsufficientFunds appears on exit orders (stoploss or long sell/short buy).
|
||||||
Tries to walk the stored orders and sell them off eventually.
|
Tries to walk the stored orders and sell them off eventually.
|
||||||
"""
|
"""
|
||||||
logger.info(f"Trying to refind lost order for {trade}")
|
logger.info(f"Trying to refind lost order for {trade}")
|
||||||
@ -330,8 +343,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if not order.ft_is_open:
|
if not order.ft_is_open:
|
||||||
logger.debug(f"Order {order} is no longer open.")
|
logger.debug(f"Order {order} is no longer open.")
|
||||||
continue
|
continue
|
||||||
if order.ft_order_side == 'buy':
|
if order.ft_order_side == trade.enter_side:
|
||||||
# Skip buy side - this is handled by reupdate_buy_order_fees
|
# Skip buy side - this is handled by reupdate_enter_order_fees
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
|
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
|
||||||
@ -340,7 +353,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if fo and fo['status'] == 'open':
|
if fo and fo['status'] == 'open':
|
||||||
# Assume this as the open stoploss order
|
# Assume this as the open stoploss order
|
||||||
trade.stoploss_order_id = order.order_id
|
trade.stoploss_order_id = order.order_id
|
||||||
elif order.ft_order_side == 'sell':
|
elif order.ft_order_side == trade.exit_side:
|
||||||
if fo and fo['status'] == 'open':
|
if fo and fo['status'] == 'open':
|
||||||
# Assume this as the open order
|
# Assume this as the open order
|
||||||
trade.open_order_id = order.order_id
|
trade.open_order_id = order.order_id
|
||||||
@ -358,7 +371,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
def enter_positions(self) -> int:
|
def enter_positions(self) -> int:
|
||||||
"""
|
"""
|
||||||
Tries to execute buy orders for new trades (positions)
|
Tries to execute long buy/short sell orders for new trades (positions)
|
||||||
"""
|
"""
|
||||||
trades_created = 0
|
trades_created = 0
|
||||||
|
|
||||||
@ -374,7 +387,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
if not whitelist:
|
if not whitelist:
|
||||||
logger.info("No currency pair in active pair whitelist, "
|
logger.info("No currency pair in active pair whitelist, "
|
||||||
"but checking to sell open trades.")
|
"but checking to exit open trades.")
|
||||||
return trades_created
|
return trades_created
|
||||||
if PairLocks.is_global_lock():
|
if PairLocks.is_global_lock():
|
||||||
lock = PairLocks.get_pair_longest_lock('*')
|
lock = PairLocks.get_pair_longest_lock('*')
|
||||||
@ -393,16 +406,16 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.warning('Unable to create trade for %s: %s', pair, exception)
|
logger.warning('Unable to create trade for %s: %s', pair, exception)
|
||||||
|
|
||||||
if not trades_created:
|
if not trades_created:
|
||||||
logger.debug("Found no buy signals for whitelisted currencies. Trying again...")
|
logger.debug("Found no enter signals for whitelisted currencies. Trying again...")
|
||||||
|
|
||||||
return trades_created
|
return trades_created
|
||||||
|
|
||||||
def create_trade(self, pair: str) -> bool:
|
def create_trade(self, pair: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check the implemented trading strategy for buy signals.
|
Check the implemented trading strategy for enter signals.
|
||||||
|
|
||||||
If the pair triggers the buy signal a new trade record gets created
|
If the pair triggers the enter signal a new trade record gets created
|
||||||
and the buy-order opening the trade gets issued towards the exchange.
|
and the enter-order opening the trade gets issued towards the exchange.
|
||||||
|
|
||||||
:return: True if a trade has been created.
|
:return: True if a trade has been created.
|
||||||
"""
|
"""
|
||||||
@ -433,17 +446,45 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.strategy.timeframe,
|
self.strategy.timeframe,
|
||||||
analyzed_df
|
analyzed_df
|
||||||
)
|
)
|
||||||
|
(short, exit_short) = (False, False)
|
||||||
|
# = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)
|
||||||
|
# #TODO: Update strategy class to get these values
|
||||||
|
|
||||||
if buy and not sell:
|
open_buy_signal = buy and not sell
|
||||||
|
open_short_signal = short and not exit_short
|
||||||
|
|
||||||
|
# TODO: For a market making strategy, this condition should be removed
|
||||||
|
if (open_buy_signal or open_short_signal):
|
||||||
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
|
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
|
||||||
|
if not stake_amount:
|
||||||
|
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
|
||||||
|
return False
|
||||||
|
# leverage = 1.0 # TODO: get leverage from somewhere
|
||||||
|
logger.info(
|
||||||
|
f"{'Buy' if open_buy_signal else 'Short'} signal found: "
|
||||||
|
f"about create a new trade for {pair} with stake_amount: "
|
||||||
|
f"{stake_amount} ..."
|
||||||
|
)
|
||||||
|
|
||||||
bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {})
|
# TODO: All later code in this function
|
||||||
if ((bid_check_dom.get('enabled', False)) and
|
if open_buy_signal:
|
||||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {})
|
||||||
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
if ((bid_check_dom.get('enabled', False)) and
|
||||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||||
else:
|
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
||||||
return False
|
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# TODO-mg: Will check_depth_of_market_buy work for shorts?
|
||||||
|
short_check_dom = self.config.get(
|
||||||
|
'short_strategy', {}).get('check_depth_of_market', {})
|
||||||
|
if ((short_check_dom.get('enabled', False)) and
|
||||||
|
(short_check_dom.get('shorts_to_ask_delta', 0) > 0)):
|
||||||
|
if self._check_depth_of_market_short(pair, short_check_dom):
|
||||||
|
return self.execute_short(pair, stake_amount)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
||||||
else:
|
else:
|
||||||
@ -473,6 +514,10 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
|
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _check_depth_of_market_short(self, pair: str, conf: Dict) -> bool:
|
||||||
|
# TODO-mg: implement, use _check_depth_of_market_buy instead if possible
|
||||||
|
return False
|
||||||
|
|
||||||
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
||||||
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
|
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -596,6 +641,10 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def execute_short(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
||||||
|
forcesell: bool = False) -> bool:
|
||||||
|
return False # TODO-mg: implement
|
||||||
|
|
||||||
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
||||||
"""
|
"""
|
||||||
Sends rpc notification when a buy occurred.
|
Sends rpc notification when a buy occurred.
|
||||||
|
@ -28,13 +28,13 @@ class PairListManager():
|
|||||||
self._tickers_needed = False
|
self._tickers_needed = False
|
||||||
for pairlist_handler_config in self._config.get('pairlists', None):
|
for pairlist_handler_config in self._config.get('pairlists', None):
|
||||||
pairlist_handler = PairListResolver.load_pairlist(
|
pairlist_handler = PairListResolver.load_pairlist(
|
||||||
pairlist_handler_config['method'],
|
pairlist_handler_config['method'],
|
||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
pairlistmanager=self,
|
pairlistmanager=self,
|
||||||
config=config,
|
config=config,
|
||||||
pairlistconfig=pairlist_handler_config,
|
pairlistconfig=pairlist_handler_config,
|
||||||
pairlist_pos=len(self._pairlist_handlers)
|
pairlist_pos=len(self._pairlist_handlers)
|
||||||
)
|
)
|
||||||
self._tickers_needed |= pairlist_handler.needstickers
|
self._tickers_needed |= pairlist_handler.needstickers
|
||||||
self._pairlist_handlers.append(pairlist_handler)
|
self._pairlist_handlers.append(pairlist_handler)
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ class PairListManager():
|
|||||||
:return: pairlist - whitelisted pairs
|
:return: pairlist - whitelisted pairs
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# TODO-mg: filter for pairlists that are able to trade at the desired leverage
|
||||||
whitelist = expand_pairlist(pairlist, self._exchange.get_markets().keys(), keep_invalid)
|
whitelist = expand_pairlist(pairlist, self._exchange.get_markets().keys(), keep_invalid)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
logger.error(f"Pair whitelist contains an invalid Wildcard: {err}")
|
logger.error(f"Pair whitelist contains an invalid Wildcard: {err}")
|
||||||
|
@ -1625,7 +1625,7 @@ def test_enter_positions(mocker, default_conf, caplog) -> None:
|
|||||||
MagicMock(return_value=False))
|
MagicMock(return_value=False))
|
||||||
n = freqtrade.enter_positions()
|
n = freqtrade.enter_positions()
|
||||||
assert n == 0
|
assert n == 0
|
||||||
assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog)
|
assert log_has('Found no enter signals for whitelisted currencies. Trying again...', caplog)
|
||||||
# create_trade should be called once for every pair in the whitelist.
|
# create_trade should be called once for every pair in the whitelist.
|
||||||
assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist'])
|
assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
@ -4332,14 +4332,14 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf, fee):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
def test_reupdate_enter_order_fees(mocker, default_conf, fee, caplog):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
|
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
|
||||||
|
|
||||||
create_mock_trades(fee)
|
create_mock_trades(fee)
|
||||||
trades = Trade.get_trades().all()
|
trades = Trade.get_trades().all()
|
||||||
|
|
||||||
freqtrade.reupdate_buy_order_fees(trades[0])
|
freqtrade.reupdate_enter_order_fees(trades[0])
|
||||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||||
assert mock_uts.call_count == 1
|
assert mock_uts.call_count == 1
|
||||||
assert mock_uts.call_args_list[0][0][0] == trades[0]
|
assert mock_uts.call_args_list[0][0][0] == trades[0]
|
||||||
@ -4362,7 +4362,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
|||||||
)
|
)
|
||||||
Trade.query.session.add(trade)
|
Trade.query.session.add(trade)
|
||||||
|
|
||||||
freqtrade.reupdate_buy_order_fees(trade)
|
freqtrade.reupdate_enter_order_fees(trade)
|
||||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||||
assert mock_uts.call_count == 0
|
assert mock_uts.call_count == 0
|
||||||
assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
|
assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
|
||||||
@ -4372,7 +4372,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
|||||||
def test_handle_insufficient_funds(mocker, default_conf, fee):
|
def test_handle_insufficient_funds(mocker, default_conf, fee):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
|
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
|
||||||
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_buy_order_fees')
|
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
|
||||||
create_mock_trades(fee)
|
create_mock_trades(fee)
|
||||||
trades = Trade.get_trades().all()
|
trades = Trade.get_trades().all()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user