Add threading lock object for /forcesell

Protects against stoploss_on_exchange order recreation
in case of /forcesell (it's a timing issue, so may or may not happen).
This commit is contained in:
Matthias 2020-01-22 20:50:09 +01:00
parent 58ceda4b90
commit aad10ceee3
2 changed files with 30 additions and 23 deletions

View File

@ -7,6 +7,7 @@ import traceback
from datetime import datetime from datetime import datetime
from math import isclose from math import isclose
from os import getpid from os import getpid
from threading import Lock
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
import arrow import arrow
@ -27,7 +28,6 @@ from freqtrade.state import State
from freqtrade.strategy.interface import IStrategy, SellType from freqtrade.strategy.interface import IStrategy, SellType
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -92,6 +92,8 @@ class FreqtradeBot:
# 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.
self.rpc: RPCManager = RPCManager(self) self.rpc: RPCManager = RPCManager(self)
# Protect sell-logic from forcesell and viceversa
self._sell_lock = Lock()
def cleanup(self) -> None: def cleanup(self) -> None:
""" """
@ -132,8 +134,12 @@ class FreqtradeBot:
self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist), self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist),
self.strategy.informative_pairs()) self.strategy.informative_pairs())
# First process current opened trades (positions) # Protect from collisions with forcesell.
self.exit_positions(trades) # Without this, freqtrade my try to recreate stoploss_on_exchange orders
# while selling is in process, since telegram messages arrive in an different thread.
with self._sell_lock:
# First process current opened trades (positions)
self.exit_positions(trades)
# Then looking for buy opportunities # Then looking for buy opportunities
if self.get_free_open_trades(): if self.get_free_open_trades():
@ -748,8 +754,8 @@ class FreqtradeBot:
Check and execute sell Check and execute sell
""" """
should_sell = self.strategy.should_sell( should_sell = self.strategy.should_sell(
trade, sell_rate, datetime.utcnow(), buy, sell, trade, sell_rate, datetime.utcnow(), buy, sell,
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
) )
if should_sell.sell_flag: if should_sell.sell_flag:

View File

@ -420,26 +420,27 @@ class RPC:
if self._freqtrade.state != State.RUNNING: if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running') raise RPCException('trader is not running')
if trade_id == 'all': with self._freqtrade._sell_lock:
# Execute sell for all open orders if trade_id == 'all':
for trade in Trade.get_open_trades(): # Execute sell for all open orders
_exec_forcesell(trade) for trade in Trade.get_open_trades():
_exec_forcesell(trade)
Trade.session.flush()
self._freqtrade.wallets.update()
return {'result': 'Created sell orders for all open trades.'}
# Query for trade
trade = Trade.get_trades(
trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True), ]
).first()
if not trade:
logger.warning('forcesell: Invalid argument received')
raise RPCException('invalid argument')
_exec_forcesell(trade)
Trade.session.flush() Trade.session.flush()
self._freqtrade.wallets.update() self._freqtrade.wallets.update()
return {'result': 'Created sell orders for all open trades.'} return {'result': f'Created sell order for trade {trade_id}.'}
# Query for trade
trade = Trade.get_trades(
trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True), ]
).first()
if not trade:
logger.warning('forcesell: Invalid argument received')
raise RPCException('invalid argument')
_exec_forcesell(trade)
Trade.session.flush()
self._freqtrade.wallets.update()
return {'result': f'Created sell order for trade {trade_id}.'}
def _rpc_forcebuy(self, pair: str, price: Optional[float]) -> Optional[Trade]: def _rpc_forcebuy(self, pair: str, price: Optional[float]) -> Optional[Trade]:
""" """