Merge branch 'feat/short' into funding-fee-dry-run

This commit is contained in:
Sam Germain 2021-11-14 03:15:22 -06:00
commit e7fad04eb9
17 changed files with 104 additions and 128 deletions

View File

@ -23,4 +23,6 @@ class Bibox(Exchange):
@property @property
def _ccxt_config(self) -> Dict: def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization. # Parameters to add directly to ccxt sync/async initialization.
return {"has": {"fetchCurrencies": False}} config = {"has": {"fetchCurrencies": False}}
config.update(super()._ccxt_config)
return config

View File

@ -28,33 +28,17 @@ class Binance(Exchange):
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "fromId", "trades_pagination_arg": "fromId",
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
"ccxt_futures_name": "future"
} }
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported # TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported # (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported # (TradingMode.FUTURES, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED)
] ]
@property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
if self.trading_mode == TradingMode.MARGIN:
return {
"options": {
"defaultType": "margin"
}
}
elif self.trading_mode == TradingMode.FUTURES:
return {
"options": {
"defaultType": "future"
}
}
else:
return {}
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
""" """
Verify stop_loss against stoploss-order value (limit or price) Verify stop_loss against stoploss-order value (limit or price)

View File

@ -21,10 +21,12 @@ class Bybit(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit": 200, "ohlcv_candle_limit": 200,
"ccxt_futures_name": "linear"
} }
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
# (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported # TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported # (TradingMode.FUTURES, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED)
] ]

View File

@ -69,7 +69,8 @@ class Exchange:
"trades_pagination_arg": "since", "trades_pagination_arg": "since",
"l2_limit_range": None, "l2_limit_range": None,
"l2_limit_range_required": True, # Allow Empty L2 limit (kucoin) "l2_limit_range_required": True, # Allow Empty L2 limit (kucoin)
"mark_ohlcv_price": "mark" "mark_ohlcv_price": "mark",
"ccxt_futures_name": "swap"
} }
_ft_has: Dict = {} _ft_has: Dict = {}
@ -231,7 +232,20 @@ class Exchange:
@property @property
def _ccxt_config(self) -> Dict: def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization. # Parameters to add directly to ccxt sync/async initialization.
return {} if self.trading_mode == TradingMode.MARGIN:
return {
"options": {
"defaultType": "margin"
}
}
elif self.trading_mode == TradingMode.FUTURES:
return {
"options": {
"defaultType": self._ft_has["ccxt_futures_name"]
}
}
else:
return {}
@property @property
def name(self) -> str: def name(self) -> str:

View File

@ -25,8 +25,9 @@ class Ftx(Exchange):
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported # TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.CROSS) # TODO-lev: Uncomment once supported # (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.CROSS)
] ]
def market_is_tradable(self, market: Dict[str, Any]) -> bool: def market_is_tradable(self, market: Dict[str, Any]) -> bool:

View File

@ -28,29 +28,12 @@ class Gateio(Exchange):
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported # TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported # (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported # (TradingMode.FUTURES, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED)
] ]
@property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
if self.trading_mode == TradingMode.MARGIN:
return {
"options": {
"defaultType": "margin"
}
}
elif self.trading_mode == TradingMode.FUTURES:
return {
"options": {
"defaultType": "swap"
}
}
else:
return {}
def validate_ordertypes(self, order_types: Dict) -> None: def validate_ordertypes(self, order_types: Dict) -> None:
super().validate_ordertypes(order_types) super().validate_ordertypes(order_types)

View File

@ -26,8 +26,9 @@ class Kraken(Exchange):
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported # TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.CROSS) # TODO-lev: No CCXT support # (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.CROSS)
] ]
def market_is_tradable(self, market: Dict[str, Any]) -> bool: def market_is_tradable(self, market: Dict[str, Any]) -> bool:

View File

@ -20,25 +20,8 @@ class Okex(Exchange):
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported # TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported # (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported # (TradingMode.FUTURES, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED)
] ]
@property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
if self.trading_mode == TradingMode.MARGIN:
return {
"options": {
"defaultType": "margin"
}
}
elif self.trading_mode == TradingMode.FUTURES:
return {
"options": {
"defaultType": "swap"
}
}
else:
return {}

View File

@ -923,8 +923,7 @@ class FreqtradeBot(LoggingMixin):
Check if trade is fulfilled in which case the stoploss Check if trade is fulfilled in which case the stoploss
on exchange should be added immediately if stoploss on exchange on exchange should be added immediately if stoploss on exchange
is enabled. is enabled.
# TODO-lev: liquidation price will always be on exchange, even though # TODO-lev: liquidation price always on exchange, even without stoploss_on_exchange
# TODO-lev: stoploss_on_exchange might not be enabled
""" """
logger.debug('Handling stoploss on exchange %s ...', trade) logger.debug('Handling stoploss on exchange %s ...', trade)
@ -1523,7 +1522,7 @@ class FreqtradeBot(LoggingMixin):
self.wallets.update() self.wallets.update()
if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount: if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount:
# Eat into dust if we own more than base currency # Eat into dust if we own more than base currency
# TODO-lev: won't be in base currency for shorts # TODO-lev: settle currency for futures
logger.info(f"Fee amount for {trade} was in base currency - " logger.info(f"Fee amount for {trade} was in base currency - "
f"Eating Fee {fee_abs} into dust.") f"Eating Fee {fee_abs} into dust.")
elif fee_abs != 0: elif fee_abs != 0:

View File

@ -916,8 +916,8 @@ class Trade(_DECL_BASE, LocalTrade):
max_rate = Column(Float, nullable=True, default=0.0) max_rate = Column(Float, nullable=True, default=0.0)
# Lowest price reached # Lowest price reached
min_rate = Column(Float, nullable=True) min_rate = Column(Float, nullable=True)
sell_reason = Column(String(100), nullable=True) # TODO-lev: Change to close_reason sell_reason = Column(String(100), nullable=True)
sell_order_status = Column(String(100), nullable=True) # TODO-lev: Change to close_order_status sell_order_status = Column(String(100), nullable=True)
strategy = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True)
buy_tag = Column(String(100), nullable=True) buy_tag = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True) timeframe = Column(Integer, nullable=True)

View File

@ -32,7 +32,7 @@ class StoplossGuard(IProtection):
def _reason(self) -> str: def _reason(self) -> str:
""" """
LockReason to use LockReason to use
#TODO-lev: check if min is the right word for shorts # TODO-lev: check if min is the right word for shorts
""" """
return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, ' return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, '
f'locking for {self._stop_duration} min.') f'locking for {self._stop_duration} min.')

View File

@ -488,7 +488,7 @@ def leverage_trade(fee):
open_order_id='dry_run_leverage_buy_12368', open_order_id='dry_run_leverage_buy_12368',
strategy='DefaultStrategy', strategy='DefaultStrategy',
timeframe=5, timeframe=5,
sell_reason='sell_signal', # TODO-lev: Update to exit/close reason sell_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300),
close_date=datetime.now(tz=timezone.utc), close_date=datetime.now(tz=timezone.utc),
interest_rate=0.0005 interest_rate=0.0005

View File

@ -3253,16 +3253,16 @@ def test_validate_trading_mode_and_collateral(
("binance", "spot", {}), ("binance", "spot", {}),
("binance", "margin", {"options": {"defaultType": "margin"}}), ("binance", "margin", {"options": {"defaultType": "margin"}}),
("binance", "futures", {"options": {"defaultType": "future"}}), ("binance", "futures", {"options": {"defaultType": "future"}}),
("kraken", "spot", {}), ("bibox", "spot", {"has": {"fetchCurrencies": False}}),
("kraken", "margin", {}), ("bibox", "margin", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "margin"}}),
("kraken", "futures", {}), ("bibox", "futures", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "swap"}}),
("ftx", "spot", {}), ("bybit", "futures", {"options": {"defaultType": "linear"}}),
("ftx", "margin", {}), ("ftx", "futures", {"options": {"defaultType": "swap"}}),
("ftx", "futures", {}),
("bittrex", "spot", {}),
("gateio", "spot", {}),
("gateio", "margin", {"options": {"defaultType": "margin"}}),
("gateio", "futures", {"options": {"defaultType": "swap"}}), ("gateio", "futures", {"options": {"defaultType": "swap"}}),
("hitbtc", "futures", {"options": {"defaultType": "swap"}}),
("kraken", "futures", {"options": {"defaultType": "swap"}}),
("kucoin", "futures", {"options": {"defaultType": "swap"}}),
("okex", "futures", {"options": {"defaultType": "swap"}}),
]) ])
def test__ccxt_config( def test__ccxt_config(
default_conf, default_conf,

View File

@ -586,10 +586,10 @@ def test_api_trades(botclient, mocker, fee, markets, is_short):
assert rc.json()['total_trades'] == 2 assert rc.json()['total_trades'] == 2
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
def test_api_trade_single(botclient, mocker, fee, ticker, markets): def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot) patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets), markets=PropertyMock(return_value=markets),
@ -599,7 +599,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
assert_response(rc, 404) assert_response(rc, 404)
assert rc.json()['detail'] == 'Trade not found.' assert rc.json()['detail'] == 'Trade not found.'
create_mock_trades(fee, False) create_mock_trades(fee, is_short=is_short)
Trade.query.session.flush() Trade.query.session.flush()
rc = client_get(client, f"{BASE_URI}/trade/3") rc = client_get(client, f"{BASE_URI}/trade/3")
@ -607,10 +607,10 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
assert rc.json()['trade_id'] == 3 assert rc.json()['trade_id'] == 3
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
def test_api_delete_trade(botclient, mocker, fee, markets): def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot) patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
stoploss_mock = MagicMock() stoploss_mock = MagicMock()
cancel_mock = MagicMock() cancel_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -749,10 +749,10 @@ def test_api_profit(botclient, mocker, ticker, fee, markets):
} }
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
def test_api_stats(botclient, mocker, ticker, fee, markets,): def test_api_stats(botclient, mocker, ticker, fee, markets, is_short):
ftbot, client = botclient ftbot, client = botclient
patch_get_signal(ftbot) patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
@ -766,7 +766,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
assert 'durations' in rc.json() assert 'durations' in rc.json()
assert 'sell_reasons' in rc.json() assert 'sell_reasons' in rc.json()
create_mock_trades(fee, False) create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/stats") rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200) assert_response(rc, 200)

View File

@ -1294,8 +1294,8 @@ def test_telegram_trades(mocker, update, default_conf, fee):
msg_mock.call_args_list[0][0][0])) msg_mock.call_args_list[0][0][0]))
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
def test_telegram_delete_trade(mocker, update, default_conf, fee): def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
context = MagicMock() context = MagicMock()
@ -1305,7 +1305,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0] assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock() msg_mock.reset_mock()
create_mock_trades(fee, False) create_mock_trades(fee, is_short)
context = MagicMock() context = MagicMock()
context.args = [1] context.args = [1]

View File

@ -1,5 +1,6 @@
from datetime import datetime from datetime import datetime
import pytest
from pandas import DataFrame from pandas import DataFrame
from freqtrade.persistence.models import Trade from freqtrade.persistence.models import Trade
@ -7,7 +8,7 @@ from freqtrade.persistence.models import Trade
from .strats.strategy_test_v3 import StrategyTestV3 from .strats.strategy_test_v3 import StrategyTestV3
def test_strategy_test_v2_structure(): def test_strategy_test_v3_structure():
assert hasattr(StrategyTestV3, 'minimal_roi') assert hasattr(StrategyTestV3, 'minimal_roi')
assert hasattr(StrategyTestV3, 'stoploss') assert hasattr(StrategyTestV3, 'stoploss')
assert hasattr(StrategyTestV3, 'timeframe') assert hasattr(StrategyTestV3, 'timeframe')
@ -16,7 +17,11 @@ def test_strategy_test_v2_structure():
assert hasattr(StrategyTestV3, 'populate_sell_trend') assert hasattr(StrategyTestV3, 'populate_sell_trend')
def test_strategy_test_v2(result, fee): @pytest.mark.parametrize('is_short,side', [
(True, 'short'),
(False, 'long'),
])
def test_strategy_test_v3(result, fee, is_short, side):
strategy = StrategyTestV3({}) strategy = StrategyTestV3({})
metadata = {'pair': 'ETH/BTC'} metadata = {'pair': 'ETH/BTC'}
@ -32,16 +37,18 @@ def test_strategy_test_v2(result, fee):
open_rate=19_000, open_rate=19_000,
amount=0.1, amount=0.1,
pair='ETH/BTC', pair='ETH/BTC',
fee_open=fee.return_value fee_open=fee.return_value,
is_short=is_short
) )
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1, assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', rate=20000, time_in_force='gtc',
current_time=datetime.utcnow(), side='long') is True current_time=datetime.utcnow(),
side=side) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1, assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', sell_reason='roi', rate=20000, time_in_force='gtc', sell_reason='roi',
current_time=datetime.utcnow()) is True current_time=datetime.utcnow(),
side=side) is True
# TODO-lev: Test for shorts?
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(), assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
current_rate=20_000, current_profit=0.05) == strategy.stoploss current_rate=20_000, current_profit=0.05) == strategy.stoploss

View File

@ -1907,12 +1907,12 @@ def test_get_total_closed_profit(fee, use_db):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
@pytest.mark.parametrize('use_db', [True, False]) @pytest.mark.parametrize('use_db', [True, False])
def test_get_trades_proxy(fee, use_db): def test_get_trades_proxy(fee, use_db, is_short):
Trade.use_db = use_db Trade.use_db = use_db
Trade.reset_trades() Trade.reset_trades()
create_mock_trades(fee, False, use_db) create_mock_trades(fee, is_short, use_db)
trades = Trade.get_trades_proxy() trades = Trade.get_trades_proxy()
assert len(trades) == 6 assert len(trades) == 6
@ -2042,48 +2042,48 @@ def test_update_order_from_ccxt(caplog):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
def test_select_order(fee): def test_select_order(fee, is_short):
create_mock_trades(fee, False) create_mock_trades(fee, is_short)
trades = Trade.get_trades().all() trades = Trade.get_trades().all()
# Open buy order, no sell order # Open buy order, no sell order
order = trades[0].select_order('buy', True) order = trades[0].select_order(trades[0].enter_side, True)
assert order is None assert order is None
order = trades[0].select_order('buy', False) order = trades[0].select_order(trades[0].enter_side, False)
assert order is not None assert order is not None
order = trades[0].select_order('sell', None) order = trades[0].select_order(trades[0].exit_side, None)
assert order is None assert order is None
# closed buy order, and open sell order # closed buy order, and open sell order
order = trades[1].select_order('buy', True) order = trades[1].select_order(trades[1].enter_side, True)
assert order is None assert order is None
order = trades[1].select_order('buy', False) order = trades[1].select_order(trades[1].enter_side, False)
assert order is not None assert order is not None
order = trades[1].select_order('buy', None) order = trades[1].select_order(trades[1].enter_side, None)
assert order is not None assert order is not None
order = trades[1].select_order('sell', True) order = trades[1].select_order(trades[1].exit_side, True)
assert order is None assert order is None
order = trades[1].select_order('sell', False) order = trades[1].select_order(trades[1].exit_side, False)
assert order is not None assert order is not None
# Has open buy order # Has open buy order
order = trades[3].select_order('buy', True) order = trades[3].select_order(trades[3].enter_side, True)
assert order is not None assert order is not None
order = trades[3].select_order('buy', False) order = trades[3].select_order(trades[3].enter_side, False)
assert order is None assert order is None
# Open sell order # Open sell order
order = trades[4].select_order('buy', True) order = trades[4].select_order(trades[4].enter_side, True)
assert order is None assert order is None
order = trades[4].select_order('buy', False) order = trades[4].select_order(trades[4].enter_side, False)
assert order is not None assert order is not None
order = trades[4].select_order('sell', True) order = trades[4].select_order(trades[4].exit_side, True)
assert order is not None assert order is not None
assert order.ft_order_side == 'stoploss' assert order.ft_order_side == 'stoploss'
order = trades[4].select_order('sell', False) order = trades[4].select_order(trades[4].exit_side, False)
assert order is None assert order is None