Merge branch 'feat/short' into max-amount
This commit is contained in:
commit
dc6cb445fd
@ -9,3 +9,4 @@ class MarginMode(Enum):
|
|||||||
"""
|
"""
|
||||||
CROSS = "cross"
|
CROSS = "cross"
|
||||||
ISOLATED = "isolated"
|
ISOLATED = "isolated"
|
||||||
|
NONE = ''
|
||||||
|
@ -941,11 +941,10 @@ class Exchange:
|
|||||||
side: str,
|
side: str,
|
||||||
amount: float,
|
amount: float,
|
||||||
rate: float,
|
rate: float,
|
||||||
|
leverage: float,
|
||||||
reduceOnly: bool = False,
|
reduceOnly: bool = False,
|
||||||
leverage: float = 1.0,
|
|
||||||
time_in_force: str = 'gtc',
|
time_in_force: str = 'gtc',
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
# TODO-lev: remove default for leverage
|
|
||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate, leverage)
|
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate, leverage)
|
||||||
return dry_order
|
return dry_order
|
||||||
|
@ -605,7 +605,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self,
|
self,
|
||||||
pair: str,
|
pair: str,
|
||||||
open_rate: float,
|
open_rate: float,
|
||||||
amount: float,
|
amount: float, # quote currency, includes leverage
|
||||||
leverage: float,
|
leverage: float,
|
||||||
is_short: bool
|
is_short: bool
|
||||||
) -> Tuple[float, Optional[float]]:
|
) -> Tuple[float, Optional[float]]:
|
||||||
@ -1285,7 +1285,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# to the order dict acquired before cancelling.
|
# to the order dict acquired before cancelling.
|
||||||
# we need to fall back to the values from order if corder does not contain these keys.
|
# we need to fall back to the values from order if corder does not contain these keys.
|
||||||
trade.amount = filled_amount
|
trade.amount = filled_amount
|
||||||
# TODO-lev: Check edge cases, we don't want to make leverage > 1.0 if we don't have to
|
# * Check edge cases, we don't want to make leverage > 1.0 if we don't have to
|
||||||
|
# * (for leverage modes which aren't isolated futures)
|
||||||
|
|
||||||
trade.stake_amount = trade.amount * trade.open_rate
|
trade.stake_amount = trade.amount * trade.open_rate
|
||||||
self.update_trade_state(trade, trade.open_order_id, corder)
|
self.update_trade_state(trade, trade.open_order_id, corder)
|
||||||
@ -1352,13 +1353,14 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
:return: amount to sell
|
:return: amount to sell
|
||||||
:raise: DependencyException: if available balance is not within 2% of the available amount.
|
:raise: DependencyException: if available balance is not within 2% of the available amount.
|
||||||
"""
|
"""
|
||||||
# TODO-lev Maybe update?
|
|
||||||
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
trade_base_currency = self.exchange.get_pair_base_currency(pair)
|
trade_base_currency = self.exchange.get_pair_base_currency(pair)
|
||||||
wallet_amount = self.wallets.get_free(trade_base_currency)
|
wallet_amount = self.wallets.get_free(trade_base_currency)
|
||||||
logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
|
logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
|
||||||
if wallet_amount >= amount:
|
# TODO-lev: Get wallet amount + value of positions
|
||||||
|
if wallet_amount >= amount or self.trading_mode == TradingMode.FUTURES:
|
||||||
|
# A safe exit amount isn't needed for futures, you can just exit/close the position
|
||||||
return amount
|
return amount
|
||||||
elif wallet_amount > amount * 0.98:
|
elif wallet_amount > amount * 0.98:
|
||||||
logger.info(f"{pair} - Falling back to wallet-amount {wallet_amount} -> {amount}.")
|
logger.info(f"{pair} - Falling back to wallet-amount {wallet_amount} -> {amount}.")
|
||||||
@ -1436,6 +1438,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
side=trade.exit_side,
|
side=trade.exit_side,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
rate=limit,
|
rate=limit,
|
||||||
|
leverage=trade.leverage,
|
||||||
reduceOnly=self.trading_mode == TradingMode.FUTURES,
|
reduceOnly=self.trading_mode == TradingMode.FUTURES,
|
||||||
time_in_force=time_in_force
|
time_in_force=time_in_force
|
||||||
)
|
)
|
||||||
|
@ -333,8 +333,12 @@ class Backtesting:
|
|||||||
df_analyzed.loc[:, col] = 0 if not tag_col else None
|
df_analyzed.loc[:, col] = 0 if not tag_col else None
|
||||||
|
|
||||||
# Update dataprovider cache
|
# Update dataprovider cache
|
||||||
self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed, CandleType.SPOT)
|
self.dataprovider._set_cached_df(
|
||||||
# TODO-lev: Candle-type should be conditional, either "spot" or futures
|
pair,
|
||||||
|
self.timeframe,
|
||||||
|
df_analyzed,
|
||||||
|
self.config['candle_type_def']
|
||||||
|
)
|
||||||
|
|
||||||
df_analyzed = df_analyzed.drop(df_analyzed.head(1).index)
|
df_analyzed = df_analyzed.drop(df_analyzed.head(1).index)
|
||||||
|
|
||||||
@ -499,7 +503,7 @@ class Backtesting:
|
|||||||
sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime()
|
sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime()
|
||||||
|
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
# TODO-lev: Other fees / liquidation price?
|
# TODO-lev: liquidation price?
|
||||||
trade.funding_fees = self.exchange.calculate_funding_fees(
|
trade.funding_fees = self.exchange.calculate_funding_fees(
|
||||||
self.futures_data[trade.pair],
|
self.futures_data[trade.pair],
|
||||||
amount=trade.amount,
|
amount=trade.amount,
|
||||||
|
@ -569,7 +569,6 @@ class LocalTrade():
|
|||||||
payment = "BUY" if self.is_short else "SELL"
|
payment = "BUY" if self.is_short else "SELL"
|
||||||
# * On margin shorts, you buy a little bit more than the amount (amount + interest)
|
# * On margin shorts, you buy a little bit more than the amount (amount + interest)
|
||||||
logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.')
|
logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.')
|
||||||
# TODO-lev: Double check this
|
|
||||||
self.close(safe_value_fallback(order, 'average', 'price'))
|
self.close(safe_value_fallback(order, 'average', 'price'))
|
||||||
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
|
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
|
||||||
self.stoploss_order_id = None
|
self.stoploss_order_id = None
|
||||||
|
@ -431,7 +431,6 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
|
|||||||
)
|
)
|
||||||
fig.add_trace(candles, 1, 1)
|
fig.add_trace(candles, 1, 1)
|
||||||
|
|
||||||
# TODO-lev: Needs short equivalent
|
|
||||||
if 'enter_long' in data.columns:
|
if 'enter_long' in data.columns:
|
||||||
df_buy = data[data['enter_long'] == 1]
|
df_buy = data[data['enter_long'] == 1]
|
||||||
if len(df_buy) > 0:
|
if len(df_buy) > 0:
|
||||||
@ -537,7 +536,7 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
|
|||||||
"Profit per pair",
|
"Profit per pair",
|
||||||
"Parallelism",
|
"Parallelism",
|
||||||
"Underwater",
|
"Underwater",
|
||||||
])
|
])
|
||||||
fig['layout'].update(title="Freqtrade Profit plot")
|
fig['layout'].update(title="Freqtrade Profit plot")
|
||||||
fig['layout']['yaxis1'].update(title='Price')
|
fig['layout']['yaxis1'].update(title='Price')
|
||||||
fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}')
|
fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}')
|
||||||
|
@ -1227,7 +1227,8 @@ def test_buy_dry_run(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy",
|
order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy",
|
||||||
amount=1, rate=200, time_in_force='gtc')
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force='gtc')
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'dry_run_buy_' in order['id']
|
assert 'dry_run_buy_' in order['id']
|
||||||
|
|
||||||
@ -1252,7 +1253,8 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -1271,7 +1273,9 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
|||||||
side="buy",
|
side="buy",
|
||||||
amount=1,
|
amount=1,
|
||||||
rate=200,
|
rate=200,
|
||||||
time_in_force=time_in_force)
|
leverage=1.0,
|
||||||
|
time_in_force=time_in_force
|
||||||
|
)
|
||||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||||
assert api_mock.create_order.call_args[0][1] == order_type
|
assert api_mock.create_order.call_args[0][1] == order_type
|
||||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||||
@ -1283,31 +1287,36 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
|||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy",
|
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype='market', side="buy",
|
exchange.create_order(pair='ETH/BTC', ordertype='market', side="buy",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
@ -1331,7 +1340,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
|
|||||||
time_in_force = 'ioc'
|
time_in_force = 'ioc'
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -1348,7 +1358,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
|
|||||||
time_in_force = 'ioc'
|
time_in_force = 'ioc'
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -1367,7 +1378,7 @@ def test_sell_dry_run(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype='limit',
|
order = exchange.create_order(pair='ETH/BTC', ordertype='limit',
|
||||||
side="sell", amount=1, rate=200)
|
side="sell", amount=1, rate=200, leverage=1.0)
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'dry_run_sell_' in order['id']
|
assert 'dry_run_sell_' in order['id']
|
||||||
|
|
||||||
@ -1392,7 +1403,7 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
||||||
side="sell", amount=1, rate=200)
|
side="sell", amount=1, rate=200, leverage=1.0)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -1406,7 +1417,8 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
|||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
order_type = 'limit'
|
order_type = 'limit'
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
||||||
side="sell", amount=1, rate=200)
|
side="sell", amount=1, rate=200,
|
||||||
|
leverage=1.0)
|
||||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||||
assert api_mock.create_order.call_args[0][1] == order_type
|
assert api_mock.create_order.call_args[0][1] == order_type
|
||||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||||
@ -1417,28 +1429,33 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
|||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
|
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200,
|
||||||
|
leverage=1.0)
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200)
|
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200,
|
||||||
|
leverage=1.0)
|
||||||
|
|
||||||
# Market orders don't require price, so the behaviour is slightly different
|
# Market orders don't require price, so the behaviour is slightly different
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200)
|
exchange.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200,
|
||||||
|
leverage=1.0)
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
|
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200,
|
||||||
|
leverage=1.0)
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
|
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200,
|
||||||
|
leverage=1.0)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
@ -1462,7 +1479,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
|
|||||||
time_in_force = 'ioc'
|
time_in_force = 'ioc'
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
|
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -1478,7 +1496,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
|
|||||||
order_type = 'market'
|
order_type = 'market'
|
||||||
time_in_force = 'ioc'
|
time_in_force = 'ioc'
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
|
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
amount=1, rate=200, leverage=1.0,
|
||||||
|
time_in_force=time_in_force)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -3792,9 +3811,10 @@ def test__fetch_and_calculate_funding_fees_datetime_called(
|
|||||||
d1 = datetime.strptime("2021-09-01 00:00:00 +0000", '%Y-%m-%d %H:%M:%S %z')
|
d1 = datetime.strptime("2021-09-01 00:00:00 +0000", '%Y-%m-%d %H:%M:%S %z')
|
||||||
|
|
||||||
time_machine.move_to("2021-09-01 08:00:00 +00:00")
|
time_machine.move_to("2021-09-01 08:00:00 +00:00")
|
||||||
# TODO-lev: test this for longs
|
|
||||||
funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, True, d1)
|
funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, True, d1)
|
||||||
assert funding_fees == expected_fees
|
assert funding_fees == expected_fees
|
||||||
|
funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, False, d1)
|
||||||
|
assert funding_fees == 0 - expected_fees
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('pair,expected_size,trading_mode', [
|
@pytest.mark.parametrize('pair,expected_size,trading_mode', [
|
||||||
|
@ -32,8 +32,15 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
order = exchange.create_order(
|
||||||
amount=1, rate=200, time_in_force=time_in_force)
|
pair='ETH/BTC',
|
||||||
|
ordertype=order_type,
|
||||||
|
side="buy",
|
||||||
|
amount=1,
|
||||||
|
rate=200,
|
||||||
|
leverage=1.0,
|
||||||
|
time_in_force=time_in_force
|
||||||
|
)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -66,7 +73,7 @@ def test_sell_kraken_trading_agreement(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||||
|
|
||||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
||||||
side="sell", amount=1, rate=200)
|
side="sell", amount=1, rate=200, leverage=1.0)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
|
@ -169,7 +169,6 @@ class StrategyTestV3(IStrategy):
|
|||||||
),
|
),
|
||||||
'exit_short'] = 1
|
'exit_short'] = 1
|
||||||
|
|
||||||
# TODO-lev: Add short logic
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
||||||
|
@ -4799,9 +4799,125 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
|
|||||||
assert valid_price_at_min_alwd < proposed_price
|
assert valid_price_at_min_alwd < proposed_price
|
||||||
|
|
||||||
|
|
||||||
def test_leverage_prep():
|
@pytest.mark.parametrize(
|
||||||
# TODO-lev
|
"is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [
|
||||||
return
|
(False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
|
||||||
|
(True, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
|
||||||
|
(False, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None),
|
||||||
|
(True, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None),
|
||||||
|
(False, 'spot', 'okex', '', 5.0, 10.0, 1.0, None),
|
||||||
|
(True, 'spot', 'okex', '', 5.0, 10.0, 1.0, None),
|
||||||
|
# Binance, short
|
||||||
|
(True, 'futures', 'binance', 'isolated', 5.0, 10.0, 1.0, 11.89108910891089),
|
||||||
|
(True, 'futures', 'binance', 'isolated', 3.0, 10.0, 1.0, 13.211221122079207),
|
||||||
|
(True, 'futures', 'binance', 'isolated', 5.0, 8.0, 1.0, 9.514851485148514),
|
||||||
|
(True, 'futures', 'binance', 'isolated', 5.0, 10.0, 0.6, 12.557755775577558),
|
||||||
|
# Binance, long
|
||||||
|
(False, 'futures', 'binance', 'isolated', 5, 10, 1.0, 8.070707070707071),
|
||||||
|
(False, 'futures', 'binance', 'isolated', 5, 8, 1.0, 6.454545454545454),
|
||||||
|
(False, 'futures', 'binance', 'isolated', 3, 10, 1.0, 6.717171717171718),
|
||||||
|
(False, 'futures', 'binance', 'isolated', 5, 10, 0.6, 7.39057239057239),
|
||||||
|
# Gateio/okex, short
|
||||||
|
(True, 'futures', 'gateio', 'isolated', 5, 10, 1.0, 11.87413417771621),
|
||||||
|
(True, 'futures', 'gateio', 'isolated', 5, 10, 2.0, 11.87413417771621),
|
||||||
|
(True, 'futures', 'gateio', 'isolated', 3, 10, 1.0, 13.476180850346978),
|
||||||
|
(True, 'futures', 'gateio', 'isolated', 5, 8, 1.0, 9.499307342172967),
|
||||||
|
# Gateio/okex, long
|
||||||
|
(False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207),
|
||||||
|
(False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506),
|
||||||
|
# (True, 'futures', 'okex', 'isolated', 11.87413417771621),
|
||||||
|
# (False, 'futures', 'okex', 'isolated', 8.085708510208207),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_leverage_prep(
|
||||||
|
mocker,
|
||||||
|
default_conf_usdt,
|
||||||
|
is_short,
|
||||||
|
trading_mode,
|
||||||
|
exchange_name,
|
||||||
|
margin_mode,
|
||||||
|
leverage,
|
||||||
|
open_rate,
|
||||||
|
amount,
|
||||||
|
expected_liq,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
position = 0.2 * 5
|
||||||
|
wb: wallet balance (stake_amount if isolated)
|
||||||
|
cum_b: maintenance amount
|
||||||
|
side_1: -1 if is_short else 1
|
||||||
|
ep1: entry price
|
||||||
|
mmr_b: maintenance margin ratio
|
||||||
|
|
||||||
|
Binance, Short
|
||||||
|
leverage = 5, open_rate = 10, amount = 1.0
|
||||||
|
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
|
||||||
|
((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089
|
||||||
|
leverage = 3, open_rate = 10, amount = 1.0
|
||||||
|
((3.3333333333 + 0.01) - ((-1) * 1.0 * 10)) / ((1.0 * 0.01) - ((-1) * 1.0)) = 13.2112211220
|
||||||
|
leverage = 5, open_rate = 8, amount = 1.0
|
||||||
|
((1.6 + 0.01) - ((-1) * 1 * 8)) / ((1 * 0.01) - ((-1) * 1)) = 9.514851485148514
|
||||||
|
leverage = 5, open_rate = 10, amount = 0.6
|
||||||
|
((1.6 + 0.01) - ((-1) * 0.6 * 10)) / ((0.6 * 0.01) - ((-1) * 0.6)) = 12.557755775577558
|
||||||
|
|
||||||
|
Binance, Long
|
||||||
|
leverage = 5, open_rate = 10, amount = 1.0
|
||||||
|
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
|
||||||
|
((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071
|
||||||
|
leverage = 5, open_rate = 8, amount = 1.0
|
||||||
|
((1.6 + 0.01) - (1 * 1 * 8)) / ((1 * 0.01) - (1 * 1)) = 6.454545454545454
|
||||||
|
leverage = 3, open_rate = 10, amount = 1.0
|
||||||
|
((2 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 6.717171717171718
|
||||||
|
leverage = 5, open_rate = 10, amount = 0.6
|
||||||
|
((1.6 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 7.39057239057239
|
||||||
|
|
||||||
|
Gateio/Okex, Short
|
||||||
|
leverage = 5, open_rate = 10, amount = 1.0
|
||||||
|
(open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate))
|
||||||
|
(10 + (2 / 1.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
|
||||||
|
leverage = 5, open_rate = 10, amount = 2.0
|
||||||
|
(10 + (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
|
||||||
|
leverage = 3, open_rate = 10, amount = 1.0
|
||||||
|
(10 + (3.3333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 13.476180850346978
|
||||||
|
leverage = 5, open_rate = 8, amount = 1.0
|
||||||
|
(8 + (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 9.499307342172967
|
||||||
|
|
||||||
|
Gateio/Okex, Long
|
||||||
|
leverage = 5, open_rate = 10, amount = 1.0
|
||||||
|
(open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
|
||||||
|
(10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207
|
||||||
|
leverage = 5, open_rate = 10, amount = 2.0
|
||||||
|
(10 - (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 7.916089451810806
|
||||||
|
leverage = 3, open_rate = 10, amount = 1.0
|
||||||
|
(10 - (3.333333333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 6.738090425173506
|
||||||
|
leverage = 5, open_rate = 8, amount = 1.0
|
||||||
|
(8 - (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 6.332871561448645
|
||||||
|
"""
|
||||||
|
default_conf_usdt['trading_mode'] = trading_mode
|
||||||
|
default_conf_usdt['exchange']['name'] = exchange_name
|
||||||
|
default_conf_usdt['margin_mode'] = margin_mode
|
||||||
|
mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes')
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_exchange(mocker, id=exchange_name)
|
||||||
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
|
|
||||||
|
freqtrade.exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01))
|
||||||
|
freqtrade.exchange.name = exchange_name
|
||||||
|
# default_conf_usdt.update({
|
||||||
|
# "dry_run": False,
|
||||||
|
# })
|
||||||
|
(interest, liq) = freqtrade.leverage_prep(
|
||||||
|
pair='ETH/USDT:USDT',
|
||||||
|
open_rate=open_rate,
|
||||||
|
amount=amount,
|
||||||
|
leverage=leverage,
|
||||||
|
is_short=is_short,
|
||||||
|
)
|
||||||
|
assert interest == 0.0
|
||||||
|
if expected_liq is None:
|
||||||
|
assert liq is None
|
||||||
|
else:
|
||||||
|
isclose(expected_liq, liq)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('trading_mode,calls,t1,t2', [
|
@pytest.mark.parametrize('trading_mode,calls,t1,t2', [
|
||||||
|
Loading…
Reference in New Issue
Block a user