Merge branch 'feat/short' into max-amount

This commit is contained in:
Sam Germain 2022-02-04 04:42:38 -06:00
commit dc6cb445fd
10 changed files with 187 additions and 40 deletions

View File

@ -9,3 +9,4 @@ class MarginMode(Enum):
"""
CROSS = "cross"
ISOLATED = "isolated"
NONE = ''

View File

@ -941,11 +941,10 @@ class Exchange:
side: str,
amount: float,
rate: float,
leverage: float,
reduceOnly: bool = False,
leverage: float = 1.0,
time_in_force: str = 'gtc',
) -> Dict:
# TODO-lev: remove default for leverage
if self._config['dry_run']:
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate, leverage)
return dry_order

View File

@ -605,7 +605,7 @@ class FreqtradeBot(LoggingMixin):
self,
pair: str,
open_rate: float,
amount: float,
amount: float, # quote currency, includes leverage
leverage: float,
is_short: bool
) -> Tuple[float, Optional[float]]:
@ -1285,7 +1285,8 @@ class FreqtradeBot(LoggingMixin):
# to the order dict acquired before cancelling.
# we need to fall back to the values from order if corder does not contain these keys.
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
self.update_trade_state(trade, trade.open_order_id, corder)
@ -1352,13 +1353,14 @@ class FreqtradeBot(LoggingMixin):
:return: amount to sell
: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!
self.wallets.update()
trade_base_currency = self.exchange.get_pair_base_currency(pair)
wallet_amount = self.wallets.get_free(trade_base_currency)
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
elif wallet_amount > amount * 0.98:
logger.info(f"{pair} - Falling back to wallet-amount {wallet_amount} -> {amount}.")
@ -1436,6 +1438,7 @@ class FreqtradeBot(LoggingMixin):
side=trade.exit_side,
amount=amount,
rate=limit,
leverage=trade.leverage,
reduceOnly=self.trading_mode == TradingMode.FUTURES,
time_in_force=time_in_force
)

View File

@ -333,8 +333,12 @@ class Backtesting:
df_analyzed.loc[:, col] = 0 if not tag_col else None
# Update dataprovider cache
self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed, CandleType.SPOT)
# TODO-lev: Candle-type should be conditional, either "spot" or futures
self.dataprovider._set_cached_df(
pair,
self.timeframe,
df_analyzed,
self.config['candle_type_def']
)
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()
if self.trading_mode == TradingMode.FUTURES:
# TODO-lev: Other fees / liquidation price?
# TODO-lev: liquidation price?
trade.funding_fees = self.exchange.calculate_funding_fees(
self.futures_data[trade.pair],
amount=trade.amount,

View File

@ -569,7 +569,6 @@ class LocalTrade():
payment = "BUY" if self.is_short else "SELL"
# * 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}.')
# TODO-lev: Double check this
self.close(safe_value_fallback(order, 'average', 'price'))
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
self.stoploss_order_id = None

View File

@ -431,7 +431,6 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
)
fig.add_trace(candles, 1, 1)
# TODO-lev: Needs short equivalent
if 'enter_long' in data.columns:
df_buy = data[data['enter_long'] == 1]
if len(df_buy) > 0:
@ -537,7 +536,7 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
"Profit per pair",
"Parallelism",
"Underwater",
])
])
fig['layout'].update(title="Freqtrade Profit plot")
fig['layout']['yaxis1'].update(title='Price')
fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}')

View File

@ -1227,7 +1227,8 @@ def test_buy_dry_run(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf)
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 '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)
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 'info' in order
@ -1271,7 +1273,9 @@ def test_buy_prod(default_conf, mocker, exchange_name):
side="buy",
amount=1,
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][1] == order_type
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"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
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):
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.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):
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.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):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
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):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
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)
@ -1331,7 +1340,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
time_in_force = 'ioc'
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 'info' in order
@ -1348,7 +1358,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
time_in_force = 'ioc'
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 'info' in order
@ -1367,7 +1378,7 @@ def test_sell_dry_run(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf)
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 '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)
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 'info' in order
@ -1406,7 +1417,8 @@ def test_sell_prod(default_conf, mocker, exchange_name):
api_mock.create_order.reset_mock()
order_type = 'limit'
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][1] == order_type
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):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
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):
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.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
with pytest.raises(DependencyException):
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.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):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection"))
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):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
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)
@ -1462,7 +1479,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
time_in_force = 'ioc'
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 'info' in order
@ -1478,7 +1496,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
order_type = 'market'
time_in_force = 'ioc'
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 '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')
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)
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', [

View File

@ -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)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
order = exchange.create_order(
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 '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")
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 'info' in order

View File

@ -169,7 +169,6 @@ class StrategyTestV3(IStrategy):
),
'exit_short'] = 1
# TODO-lev: Add short logic
return dataframe
def leverage(self, pair: str, current_time: datetime, current_rate: float,

View File

@ -4799,9 +4799,125 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
assert valid_price_at_min_alwd < proposed_price
def test_leverage_prep():
# TODO-lev
return
@pytest.mark.parametrize(
"is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [
(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', [