Merge branch 'develop' into feat/short
This commit is contained in:
@@ -20,13 +20,14 @@ from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import CandleType, MarginMode, RunMode, SignalDirection, TradingMode
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.persistence import LocalTrade, Trade, init_db
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
|
||||
from freqtrade.resolvers import ExchangeResolver
|
||||
from freqtrade.worker import Worker
|
||||
from tests.conftest_trades import (leverage_trade, mock_trade_1, mock_trade_2, mock_trade_3,
|
||||
mock_trade_4, mock_trade_5, mock_trade_6, short_trade)
|
||||
from tests.conftest_trades_usdt import (mock_trade_usdt_1, mock_trade_usdt_2, mock_trade_usdt_3,
|
||||
mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6)
|
||||
mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6,
|
||||
mock_trade_usdt_7)
|
||||
|
||||
|
||||
logging.getLogger('').setLevel(logging.INFO)
|
||||
@@ -348,6 +349,8 @@ def create_mock_trades_usdt(fee, use_db: bool = True):
|
||||
trade = mock_trade_usdt_6(fee)
|
||||
add_trade(trade)
|
||||
|
||||
trade = mock_trade_usdt_7(fee)
|
||||
add_trade(trade)
|
||||
if use_db:
|
||||
Trade.commit()
|
||||
|
||||
@@ -2352,7 +2355,7 @@ def import_fails() -> None:
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def open_trade():
|
||||
return Trade(
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='binance',
|
||||
@@ -2364,11 +2367,31 @@ def open_trade():
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
)
|
||||
trade.orders = [
|
||||
Order(
|
||||
ft_order_side='buy',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
order_id='123456789',
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
price=trade.open_rate,
|
||||
average=trade.open_rate,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
cost=trade.open_rate * trade.amount,
|
||||
order_date=trade.open_date,
|
||||
order_filled_date=trade.open_date,
|
||||
)
|
||||
]
|
||||
return trade
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def open_trade_usdt():
|
||||
return Trade(
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
open_rate=2.0,
|
||||
exchange='binance',
|
||||
@@ -2380,6 +2403,26 @@ def open_trade_usdt():
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
)
|
||||
trade.orders = [
|
||||
Order(
|
||||
ft_order_side='buy',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
order_id='123456789',
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
price=trade.open_rate,
|
||||
average=trade.open_rate,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
cost=trade.open_rate * trade.amount,
|
||||
order_date=trade.open_date,
|
||||
order_filled_date=trade.open_date,
|
||||
)
|
||||
]
|
||||
return trade
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@@ -26,6 +26,7 @@ def mock_order_1(is_short: bool):
|
||||
'side': enter_side(is_short),
|
||||
'type': 'limit',
|
||||
'price': 0.123,
|
||||
'average': 0.123,
|
||||
'amount': 123.0,
|
||||
'filled': 123.0,
|
||||
'remaining': 0.0,
|
||||
|
@@ -303,3 +303,61 @@ def mock_trade_usdt_6(fee):
|
||||
o = Order.parse_from_ccxt_object(mock_order_usdt_6_sell(), 'LTC/USDT', 'sell')
|
||||
trade.orders.append(o)
|
||||
return trade
|
||||
|
||||
|
||||
def mock_order_usdt_7():
|
||||
return {
|
||||
'id': 'prod_buy_7',
|
||||
'symbol': 'LTC/USDT',
|
||||
'status': 'closed',
|
||||
'side': 'buy',
|
||||
'type': 'limit',
|
||||
'price': 10.0,
|
||||
'amount': 2.0,
|
||||
'filled': 2.0,
|
||||
'remaining': 0.0,
|
||||
}
|
||||
|
||||
|
||||
def mock_order_usdt_7_sell():
|
||||
return {
|
||||
'id': 'prod_sell_7',
|
||||
'symbol': 'LTC/USDT',
|
||||
'status': 'closed',
|
||||
'side': 'sell',
|
||||
'type': 'limit',
|
||||
'price': 8.0,
|
||||
'amount': 2.0,
|
||||
'filled': 2.0,
|
||||
'remaining': 0.0,
|
||||
}
|
||||
|
||||
|
||||
def mock_trade_usdt_7(fee):
|
||||
"""
|
||||
Simulate prod entry with open sell order
|
||||
"""
|
||||
trade = Trade(
|
||||
pair='LTC/USDT',
|
||||
stake_amount=20.0,
|
||||
amount=2.0,
|
||||
amount_requested=2.0,
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
is_open=False,
|
||||
open_rate=10.0,
|
||||
close_rate=8.0,
|
||||
close_profit=-0.2,
|
||||
close_profit_abs=-4.0,
|
||||
exchange='binance',
|
||||
strategy='SampleStrategy',
|
||||
open_order_id="prod_sell_6",
|
||||
timeframe=5,
|
||||
)
|
||||
o = Order.parse_from_ccxt_object(mock_order_usdt_7(), 'LTC/USDT', 'buy')
|
||||
trade.orders.append(o)
|
||||
o = Order.parse_from_ccxt_object(mock_order_usdt_7_sell(), 'LTC/USDT', 'sell')
|
||||
trade.orders.append(o)
|
||||
return trade
|
||||
|
@@ -84,7 +84,7 @@ EXCHANGES = {
|
||||
'futures': True,
|
||||
}
|
||||
},
|
||||
'okex': {
|
||||
'okx': {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
|
@@ -3202,9 +3202,9 @@ def test_timeframe_to_next_date():
|
||||
("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'margin', {}, False),
|
||||
("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True),
|
||||
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'spot', {}, False),
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'margin', {}, False),
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'futures', {}, True),
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'spot', {}, False),
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'margin', {}, False),
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'futures', {}, True),
|
||||
])
|
||||
def test_market_is_tradable(
|
||||
mocker, default_conf, market_symbol, base,
|
||||
@@ -3479,16 +3479,16 @@ def test_set_margin_mode(mocker, default_conf, margin_mode):
|
||||
("bittrex", TradingMode.FUTURES, MarginMode.CROSS, True),
|
||||
("bittrex", TradingMode.FUTURES, MarginMode.ISOLATED, True),
|
||||
("gateio", TradingMode.MARGIN, MarginMode.ISOLATED, True),
|
||||
("okex", TradingMode.SPOT, None, False),
|
||||
("okex", TradingMode.MARGIN, MarginMode.CROSS, True),
|
||||
("okex", TradingMode.MARGIN, MarginMode.ISOLATED, True),
|
||||
("okex", TradingMode.FUTURES, MarginMode.CROSS, True),
|
||||
("okx", TradingMode.SPOT, None, False),
|
||||
("okx", TradingMode.MARGIN, MarginMode.CROSS, True),
|
||||
("okx", TradingMode.MARGIN, MarginMode.ISOLATED, True),
|
||||
("okx", TradingMode.FUTURES, MarginMode.CROSS, True),
|
||||
|
||||
("binance", TradingMode.FUTURES, MarginMode.ISOLATED, False),
|
||||
("gateio", TradingMode.FUTURES, MarginMode.ISOLATED, False),
|
||||
|
||||
# * Remove once implemented
|
||||
("okex", TradingMode.FUTURES, MarginMode.ISOLATED, True),
|
||||
("okx", TradingMode.FUTURES, MarginMode.ISOLATED, True),
|
||||
("binance", TradingMode.MARGIN, MarginMode.CROSS, True),
|
||||
("binance", TradingMode.FUTURES, MarginMode.CROSS, True),
|
||||
("kraken", TradingMode.MARGIN, MarginMode.CROSS, True),
|
||||
@@ -3499,7 +3499,7 @@ def test_set_margin_mode(mocker, default_conf, margin_mode):
|
||||
("gateio", TradingMode.FUTURES, MarginMode.CROSS, True),
|
||||
|
||||
# * Uncomment once implemented
|
||||
# ("okex", TradingMode.FUTURES, MarginMode.ISOLATED, False),
|
||||
# ("okx", TradingMode.FUTURES, MarginMode.ISOLATED, False),
|
||||
# ("binance", TradingMode.MARGIN, MarginMode.CROSS, False),
|
||||
# ("binance", TradingMode.FUTURES, MarginMode.CROSS, False),
|
||||
# ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False),
|
||||
@@ -3539,7 +3539,7 @@ def test_validate_trading_mode_and_margin_mode(
|
||||
("hitbtc", "futures", {"options": {"defaultType": "swap"}}),
|
||||
("kraken", "futures", {"options": {"defaultType": "swap"}}),
|
||||
("kucoin", "futures", {"options": {"defaultType": "swap"}}),
|
||||
("okex", "futures", {"options": {"defaultType": "swap"}}),
|
||||
("okx", "futures", {"options": {"defaultType": "swap"}}),
|
||||
])
|
||||
def test__ccxt_config(
|
||||
default_conf,
|
||||
|
@@ -36,6 +36,8 @@ class BTContainer(NamedTuple):
|
||||
trailing_stop_positive_offset: float = 0.0
|
||||
use_sell_signal: bool = False
|
||||
use_custom_stoploss: bool = False
|
||||
custom_entry_price: Optional[float] = None
|
||||
custom_exit_price: Optional[float] = None
|
||||
leverage: float = 1.0
|
||||
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, C0330, unused-argument
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -534,11 +535,84 @@ tc33 = BTContainer(data=[
|
||||
)]
|
||||
)
|
||||
|
||||
# Test 34: (copy of test25 with leverage)
|
||||
# Test 34: Custom-entry-price below all candles should timeout - so no trade happens.
|
||||
tc34 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # timeout
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.0,
|
||||
custom_entry_price=4200, trades=[]
|
||||
)
|
||||
|
||||
# Test 35: Custom-entry-price above all candles should have rate adjusted to "entry candle high"
|
||||
tc35 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Timeout
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01,
|
||||
custom_entry_price=7200, trades=[
|
||||
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)
|
||||
]
|
||||
)
|
||||
|
||||
# Test 36: Custom-entry-price around candle low
|
||||
# Causes immediate ROI exit. This is currently expected behavior (#6261)
|
||||
# https://github.com/freqtrade/freqtrade/issues/6261
|
||||
# But may change at a later point.
|
||||
tc36 = BTContainer(data=[
|
||||
# D O H L C V B S BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Enter and immediate ROI
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.1,
|
||||
custom_entry_price=4952,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
|
||||
# Test 37: Custom exit price below all candles
|
||||
# Price adjusted to candle Low.
|
||||
tc37 = BTContainer(data=[
|
||||
# D O H L C V B S BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0],
|
||||
[2, 4900, 5250, 4900, 5100, 6172, 0, 1], # exit - but timeout
|
||||
[3, 5100, 5100, 4950, 4950, 6172, 0, 0],
|
||||
[4, 5000, 5100, 4950, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01,
|
||||
use_sell_signal=True,
|
||||
custom_exit_price=4552,
|
||||
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
# Test 38: Custom exit price above all candles
|
||||
# causes sell signal timeout
|
||||
tc38 = BTContainer(data=[
|
||||
# D O H L C V B S BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0],
|
||||
[2, 4900, 5250, 4900, 5100, 6172, 0, 1], # exit - but timeout
|
||||
[3, 5100, 5100, 4950, 4950, 6172, 0, 0],
|
||||
[4, 5000, 5100, 4950, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
|
||||
use_sell_signal=True,
|
||||
custom_exit_price=6052,
|
||||
trades=[BTrade(sell_reason=SellType.FORCE_SELL, open_tick=1, close_tick=4)]
|
||||
)
|
||||
|
||||
# Test 39: (copy of test25 with leverage)
|
||||
# Sell with signal sell in candle 3 (stoploss also triggers on this candle)
|
||||
# Stoploss at 1%.
|
||||
# Sell-signal wins over stoploss
|
||||
tc34 = BTContainer(data=[
|
||||
tc39 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||
@@ -551,6 +625,7 @@ tc34 = BTContainer(data=[
|
||||
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
|
||||
)
|
||||
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
tc1,
|
||||
@@ -587,6 +662,11 @@ TESTS = [
|
||||
tc32,
|
||||
tc33,
|
||||
tc34,
|
||||
tc35,
|
||||
tc36,
|
||||
tc37,
|
||||
tc38,
|
||||
tc39,
|
||||
# TODO-lev: Add tests for short here
|
||||
]
|
||||
|
||||
@@ -621,6 +701,10 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
backtesting._can_short = True
|
||||
backtesting.strategy.advise_entry = lambda a, m: frame
|
||||
backtesting.strategy.advise_exit = lambda a, m: frame
|
||||
if data.custom_entry_price:
|
||||
backtesting.strategy.custom_entry_price = MagicMock(return_value=data.custom_entry_price)
|
||||
if data.custom_exit_price:
|
||||
backtesting.strategy.custom_exit_price = MagicMock(return_value=data.custom_exit_price)
|
||||
backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
|
||||
backtesting.strategy.leverage = lambda **kwargs: data.leverage
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
@@ -21,6 +21,7 @@ from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import RunMode, SellType
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||
from freqtrade.misc import get_strategy_run_id
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.persistence import LocalTrade
|
||||
@@ -524,6 +525,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
# Fake 2 trades, so there's not enough amount for the next trade left.
|
||||
LocalTrade.trades_open.append(trade)
|
||||
LocalTrade.trades_open.append(trade)
|
||||
backtesting.wallets.update()
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert trade is None
|
||||
LocalTrade.trades_open.pop()
|
||||
@@ -531,6 +533,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
assert trade is not None
|
||||
|
||||
backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
|
||||
backtesting.wallets.update()
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert trade
|
||||
assert trade.stake_amount == 123.5
|
||||
@@ -659,7 +662,8 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
|
||||
assert res.sell_reason == SellType.ROI.value
|
||||
# Sell at minute 3 (not available above!)
|
||||
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
|
||||
assert round(res.close_rate, 3) == round(209.0225, 3)
|
||||
sell_order = res.select_order('sell', True)
|
||||
assert sell_order is not None
|
||||
|
||||
|
||||
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
@@ -676,6 +680,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
timerange=timerange)
|
||||
processed = backtesting.strategy.advise_all_indicators(data)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
|
||||
result = backtesting.backtest(
|
||||
processed=deepcopy(processed),
|
||||
start_date=min_date,
|
||||
@@ -769,6 +774,47 @@ def test_processed(default_conf, mocker, testdatadir) -> None:
|
||||
assert col in cols
|
||||
|
||||
|
||||
def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['use_sell_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=100000)
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
timerange = TimeRange('date', None, 1517227800, 0)
|
||||
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
|
||||
timerange=timerange)
|
||||
processed = backtesting.strategy.advise_all_indicators(data)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
|
||||
global count
|
||||
count = 0
|
||||
|
||||
def tmp_confirm_entry(pair, current_time, **kwargs):
|
||||
dp = backtesting.strategy.dp
|
||||
df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe)
|
||||
current_candle = df.iloc[-1].squeeze()
|
||||
assert current_candle['enter_long'] == 1
|
||||
|
||||
candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle['date'])
|
||||
assert candle_date == current_time
|
||||
# These asserts don't properly raise as they are nested,
|
||||
# therefore we increment count and assert for that.
|
||||
global count
|
||||
count = count + 1
|
||||
|
||||
backtesting.strategy.confirm_trade_entry = tmp_confirm_entry
|
||||
backtesting.backtest(
|
||||
processed=deepcopy(processed),
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=10,
|
||||
position_stacking=False,
|
||||
)
|
||||
assert count == 5
|
||||
|
||||
|
||||
def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatadir) -> None:
|
||||
# While this test IS a copy of test_backtest_pricecontours, it's needed to ensure
|
||||
# results do not carry-over to the next run, which is not given by using parametrize.
|
||||
@@ -1013,6 +1059,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
})
|
||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||
@@ -1124,6 +1172,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
},
|
||||
{
|
||||
@@ -1131,6 +1181,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
}
|
||||
])
|
||||
@@ -1238,6 +1290,8 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
|
||||
'config': default_conf_usdt,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
},
|
||||
{
|
||||
@@ -1245,6 +1299,8 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
|
||||
'config': default_conf_usdt,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
}
|
||||
])
|
||||
@@ -1337,6 +1393,8 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
},
|
||||
{
|
||||
@@ -1344,6 +1402,8 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
}
|
||||
])
|
||||
@@ -1405,6 +1465,8 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
})
|
||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||
|
@@ -365,6 +365,8 @@ def test_hyperopt_format_results(hyperopt):
|
||||
'locks': [],
|
||||
'final_balance': 0.02,
|
||||
'rejected_signals': 2,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'backtest_start_time': 1619718665,
|
||||
'backtest_end_time': 1619718665,
|
||||
}
|
||||
@@ -433,6 +435,8 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
'config': hyperopt_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'final_balance': 1000,
|
||||
}
|
||||
|
||||
|
@@ -86,6 +86,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
|
||||
"SharpeHyperOptLossDaily",
|
||||
"MaxDrawDownHyperOptLoss",
|
||||
"CalmarHyperOptLoss",
|
||||
"ProfitDrawDownHyperOptLoss",
|
||||
|
||||
])
|
||||
def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None:
|
||||
@@ -106,7 +107,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
|
||||
config=default_conf,
|
||||
processed=None,
|
||||
backtest_stats={'profit_total': hyperopt_results['profit_abs'].sum()}
|
||||
)
|
||||
)
|
||||
over = hl.hyperopt_loss_function(
|
||||
results_over,
|
||||
trade_count=len(results_over),
|
||||
|
@@ -84,6 +84,8 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
||||
'locks': [],
|
||||
'final_balance': 1000.02,
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'backtest_start_time': Arrow.utcnow().int_timestamp,
|
||||
'backtest_end_time': Arrow.utcnow().int_timestamp,
|
||||
'run_id': '123',
|
||||
@@ -134,6 +136,8 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
||||
'locks': [],
|
||||
'final_balance': 1000.02,
|
||||
'rejected_signals': 20,
|
||||
'timedout_entry_orders': 0,
|
||||
'timedout_exit_orders': 0,
|
||||
'backtest_start_time': Arrow.utcnow().int_timestamp,
|
||||
'backtest_end_time': Arrow.utcnow().int_timestamp,
|
||||
'run_id': '124',
|
||||
|
@@ -4,6 +4,7 @@ import logging
|
||||
import time
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
import time_machine
|
||||
|
||||
@@ -14,7 +15,7 @@ from freqtrade.persistence import Trade
|
||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||
from freqtrade.resolvers import PairListResolver
|
||||
from tests.conftest import (create_mock_trades, get_patched_exchange, get_patched_freqtradebot,
|
||||
from tests.conftest import (create_mock_trades_usdt, get_patched_exchange, get_patched_freqtradebot,
|
||||
log_has, log_has_re, num_log_has)
|
||||
|
||||
|
||||
@@ -492,7 +493,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
|
||||
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
|
||||
('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history.append(ohlcv_history),
|
||||
('LTC/BTC', '1d', CandleType.SPOT): pd.concat([ohlcv_history, ohlcv_history]),
|
||||
('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history,
|
||||
('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_vola,
|
||||
}
|
||||
@@ -714,29 +715,58 @@ def test_ShuffleFilter_init(mocker, whitelist_conf, caplog) -> None:
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_PerformanceFilter_lookback(mocker, whitelist_conf, fee, caplog) -> None:
|
||||
whitelist_conf['exchange']['pair_whitelist'].append('XRP/BTC')
|
||||
whitelist_conf['pairlists'] = [
|
||||
def test_PerformanceFilter_lookback(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
default_conf_usdt['exchange']['pair_whitelist'].extend(['ADA/USDT', 'XRP/USDT', 'ETC/USDT'])
|
||||
default_conf_usdt['pairlists'] = [
|
||||
{"method": "StaticPairList"},
|
||||
{"method": "PerformanceFilter", "minutes": 60, "min_profit": 0.01}
|
||||
]
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
exchange = get_patched_exchange(mocker, whitelist_conf)
|
||||
pm = PairListManager(exchange, whitelist_conf)
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||
pm = PairListManager(exchange, default_conf_usdt)
|
||||
pm.refresh_pairlist()
|
||||
|
||||
assert pm.whitelist == ['ETH/BTC', 'TKN/BTC', 'XRP/BTC']
|
||||
assert pm.whitelist == ['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT']
|
||||
|
||||
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||
create_mock_trades(fee, False)
|
||||
create_mock_trades_usdt(fee)
|
||||
pm.refresh_pairlist()
|
||||
assert pm.whitelist == ['XRP/BTC']
|
||||
assert pm.whitelist == ['XRP/USDT']
|
||||
assert log_has_re(r'Removing pair .* since .* is below .*', caplog)
|
||||
|
||||
# Move to "outside" of lookback window, so original sorting is restored.
|
||||
t.move_to("2021-09-01 07:00:00 +00:00")
|
||||
pm.refresh_pairlist()
|
||||
assert pm.whitelist == ['ETH/BTC', 'TKN/BTC', 'XRP/BTC']
|
||||
assert pm.whitelist == ['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT']
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
default_conf_usdt['exchange']['pair_whitelist'].extend(['ADA/USDT', 'ETC/USDT'])
|
||||
default_conf_usdt['pairlists'] = [
|
||||
{"method": "StaticPairList", "allow_inactive": True},
|
||||
{"method": "PerformanceFilter", "minutes": 60, }
|
||||
]
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||
pm = PairListManager(exchange, default_conf_usdt)
|
||||
pm.refresh_pairlist()
|
||||
|
||||
assert pm.whitelist == ['ETH/USDT', 'LTC/USDT', 'XRP/USDT',
|
||||
'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'ETC/USDT']
|
||||
|
||||
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||
create_mock_trades_usdt(fee)
|
||||
pm.refresh_pairlist()
|
||||
assert pm.whitelist == ['XRP/USDT', 'ETC/USDT', 'ETH/USDT',
|
||||
'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'LTC/USDT']
|
||||
# assert log_has_re(r'Removing pair .* since .* is below .*', caplog)
|
||||
|
||||
# Move to "outside" of lookback window, so original sorting is restored.
|
||||
t.move_to("2021-09-01 07:00:00 +00:00")
|
||||
pm.refresh_pairlist()
|
||||
assert pm.whitelist == ['ETH/USDT', 'LTC/USDT', 'XRP/USDT',
|
||||
'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'ETC/USDT']
|
||||
|
||||
|
||||
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
|
||||
@@ -1167,13 +1197,13 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf):
|
||||
{'pair': 'TKN/BTC', 'profit_ratio': -0.0501, 'count': 2},
|
||||
{'pair': 'ETH/BTC', 'profit_ratio': -0.0501, 'count': 100}],
|
||||
['TKN/BTC', 'ETH/BTC', 'LTC/BTC']),
|
||||
# Tie in performance and count, broken by alphabetical sort
|
||||
# Tie in performance and count, broken by prior sorting sort
|
||||
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
|
||||
['ETH/BTC', 'TKN/BTC', 'LTC/BTC'],
|
||||
[{'pair': 'LTC/BTC', 'profit_ratio': -0.0501, 'count': 1},
|
||||
{'pair': 'TKN/BTC', 'profit_ratio': -0.0501, 'count': 1},
|
||||
{'pair': 'ETH/BTC', 'profit_ratio': -0.0501, 'count': 1}],
|
||||
['ETH/BTC', 'LTC/BTC', 'TKN/BTC']),
|
||||
['ETH/BTC', 'TKN/BTC', 'LTC/BTC']),
|
||||
])
|
||||
def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance,
|
||||
allowlist_result, tickers, markets, ohlcv_history_list):
|
||||
|
@@ -115,7 +115,15 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'isolated_liq': None,
|
||||
'is_short': False,
|
||||
'funding_fees': 0.0,
|
||||
'trading_mode': TradingMode.SPOT
|
||||
'trading_mode': TradingMode.SPOT,
|
||||
'filled_entry_orders': [{
|
||||
'amount': 91.07468123, 'average': 1.098e-05,
|
||||
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
|
||||
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
|
||||
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
|
||||
'is_open': False, 'pair': 'ETH/BTC',
|
||||
'remaining': ANY, 'status': ANY}],
|
||||
'filled_exit_orders': [],
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||
@@ -189,7 +197,15 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'isolated_liq': None,
|
||||
'is_short': False,
|
||||
'funding_fees': 0.0,
|
||||
'trading_mode': TradingMode.SPOT
|
||||
'trading_mode': TradingMode.SPOT,
|
||||
'filled_entry_orders': [{
|
||||
'amount': 91.07468123, 'average': 1.098e-05,
|
||||
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
|
||||
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
|
||||
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
|
||||
'is_open': False, 'pair': 'ETH/BTC',
|
||||
'remaining': ANY, 'status': ANY}],
|
||||
'filled_exit_orders': [],
|
||||
}
|
||||
|
||||
|
||||
@@ -236,9 +252,13 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
assert '-0.06' == f'{fiat_profit_sum:.2f}'
|
||||
|
||||
rpc._config['position_adjustment_enable'] = True
|
||||
rpc._config['max_entry_position_adjustment'] = 3
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert "# Buys" in headers
|
||||
assert "# Entries" in headers
|
||||
assert len(result[0]) == 5
|
||||
# 4th column should be 1/4 - as 1 order filled (a total of 4 is possible)
|
||||
# 3 on top of the initial one.
|
||||
assert result[0][4] == '1/4'
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
@@ -1301,3 +1321,13 @@ def test_rpc_edge_enabled(mocker, edge_conf) -> None:
|
||||
assert ret[0]['Winrate'] == 0.66
|
||||
assert ret[0]['Expectancy'] == 1.71
|
||||
assert ret[0]['Stoploss'] == -0.02
|
||||
|
||||
|
||||
def test_rpc_health(mocker, default_conf) -> None:
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
result = rpc._health()
|
||||
assert result['last_process'] == '1970-01-01 00:00:00+00:00'
|
||||
assert result['last_process_ts'] == 0
|
||||
|
@@ -7,6 +7,7 @@ from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
@@ -1264,6 +1265,25 @@ def test_api_pair_candles(botclient, ohlcv_history):
|
||||
0.7039405, 8.885e-05, 0, 0, 0, 0, 1511686800000, None, None, None, None]
|
||||
|
||||
])
|
||||
ohlcv_history['exit_long'] = ohlcv_history['exit_long'].astype('float64')
|
||||
ohlcv_history.at[0, 'exit_long'] = float('inf')
|
||||
ohlcv_history['date1'] = ohlcv_history['date']
|
||||
ohlcv_history.at[0, 'date1'] = pd.NaT
|
||||
|
||||
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
|
||||
assert_response(rc)
|
||||
assert (rc.json()['data'] ==
|
||||
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
|
||||
None, 0, None, 0, 0, None, 1511686200000, None, None, None, None],
|
||||
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
|
||||
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, 0, 0, '2017-11-26 08:55:00',
|
||||
1511686500000, 8.893e-05, None, None, None],
|
||||
['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
|
||||
0.7039405, 8.885e-05, 0, 0.0, 0, 0, '2017-11-26 09:00:00', 1511686800000,
|
||||
None, None, None, None]
|
||||
])
|
||||
|
||||
|
||||
def test_api_pair_history(botclient, ohlcv_history):
|
||||
@@ -1540,3 +1560,14 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
|
||||
assert result['status'] == 'reset'
|
||||
assert not result['running']
|
||||
assert result['status_msg'] == 'Backtest reset'
|
||||
|
||||
|
||||
def test_health(botclient):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/health")
|
||||
|
||||
assert_response(rc)
|
||||
ret = rc.json()
|
||||
assert ret['last_process_ts'] == 0
|
||||
assert ret['last_process'] == '1970-01-01T00:00:00+00:00'
|
||||
|
@@ -24,6 +24,7 @@ from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.loggers import setup_logging
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.persistence.models import Order
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.rpc import RPCException
|
||||
from freqtrade.rpc.telegram import Telegram, authorized_only
|
||||
@@ -102,7 +103,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
|
||||
"['count'], ['locks'], ['unlock', 'delete_locks'], "
|
||||
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
|
||||
"['stopbuy'], ['whitelist'], ['blacklist'], ['blacklist_delete', 'bl_delete'], "
|
||||
"['logs'], ['edge'], ['help'], ['version']"
|
||||
"['logs'], ['edge'], ['health'], ['help'], ['version']"
|
||||
"]")
|
||||
|
||||
assert log_has(message_str, caplog)
|
||||
@@ -206,7 +207,8 @@ def test_telegram_status(default_conf, update, mocker) -> None:
|
||||
'stop_loss_ratio': -0.0001,
|
||||
'open_order': '(limit buy rem=0.00000000)',
|
||||
'is_open': True,
|
||||
'is_short': False
|
||||
'is_short': False,
|
||||
'filled_entry_orders': [],
|
||||
}]),
|
||||
)
|
||||
|
||||
@@ -222,6 +224,80 @@ def test_telegram_status(default_conf, update, mocker) -> None:
|
||||
assert status_table.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None:
|
||||
update.message.chat.id = "123"
|
||||
default_conf['telegram']['enabled'] = False
|
||||
default_conf['telegram']['chat_id'] = "123"
|
||||
default_conf['position_adjustment_enable'] = True
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_order=MagicMock(return_value=None),
|
||||
get_rate=MagicMock(return_value=0.22),
|
||||
)
|
||||
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_open_trades()
|
||||
trade = trades[0]
|
||||
trade.orders.append(Order(
|
||||
order_id='5412vbb',
|
||||
ft_order_side='buy',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
price=trade.open_rate * 0.95,
|
||||
average=trade.open_rate * 0.95,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
cost=trade.amount,
|
||||
order_date=trade.open_date,
|
||||
order_filled_date=trade.open_date,
|
||||
)
|
||||
)
|
||||
trade.recalc_trade_from_orders()
|
||||
Trade.commit()
|
||||
|
||||
telegram._status(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 4
|
||||
msg = msg_mock.call_args_list[0][0][0]
|
||||
assert re.search(r'Number of Entries.*2', msg)
|
||||
assert re.search(r'Average Entry Price', msg)
|
||||
assert re.search(r'Order filled at', msg)
|
||||
assert re.search(r'Close Date:', msg) is None
|
||||
assert re.search(r'Close Profit:', msg) is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None:
|
||||
update.message.chat.id = "123"
|
||||
default_conf['telegram']['enabled'] = False
|
||||
default_conf['telegram']['chat_id'] = "123"
|
||||
default_conf['position_adjustment_enable'] = True
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_order=MagicMock(return_value=None),
|
||||
get_rate=MagicMock(return_value=0.22),
|
||||
)
|
||||
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_trades([Trade.is_open.is_(False)])
|
||||
trade = trades[0]
|
||||
context = MagicMock()
|
||||
context.args = [str(trade.id)]
|
||||
telegram._status(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
msg = msg_mock.call_args_list[0][0][0]
|
||||
assert re.search(r'Close Date:', msg)
|
||||
assert re.search(r'Close Profit:', msg)
|
||||
|
||||
|
||||
def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
default_conf['max_open_trades'] = 3
|
||||
mocker.patch.multiple(
|
||||
|
@@ -712,14 +712,14 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
|
||||
(True, 'spot', 'binance', None, None),
|
||||
(False, 'spot', 'gateio', None, None),
|
||||
(True, 'spot', 'gateio', None, None),
|
||||
(False, 'spot', 'okex', None, None),
|
||||
(True, 'spot', 'okex', None, None),
|
||||
(False, 'spot', 'okx', None, None),
|
||||
(True, 'spot', 'okx', None, None),
|
||||
(True, 'futures', 'binance', 'isolated', 11.89108910891089),
|
||||
(False, 'futures', 'binance', 'isolated', 8.070707070707071),
|
||||
(True, 'futures', 'gateio', 'isolated', 11.87413417771621),
|
||||
(False, 'futures', 'gateio', 'isolated', 8.085708510208207),
|
||||
# (True, 'futures', 'okex', 'isolated', 11.87413417771621),
|
||||
# (False, 'futures', 'okex', 'isolated', 8.085708510208207),
|
||||
# (True, 'futures', 'okx', 'isolated', 11.87413417771621),
|
||||
# (False, 'futures', 'okx', 'isolated', 8.085708510208207),
|
||||
])
|
||||
def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
limit_order_open, is_short, trading_mode,
|
||||
@@ -735,11 +735,11 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
((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
|
||||
|
||||
exchange_name = gateio/okex, is_short = true
|
||||
exchange_name = gateio/okx, is_short = true
|
||||
(open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate))
|
||||
(10 + (2 / 1)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
|
||||
|
||||
exchange_name = gateio/okex, is_short = false
|
||||
exchange_name = gateio/okx, is_short = false
|
||||
(open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
|
||||
(10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207
|
||||
"""
|
||||
@@ -791,7 +791,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
call_args = enter_mm.call_args_list[0][1]
|
||||
assert call_args['pair'] == pair
|
||||
assert call_args['rate'] == bid
|
||||
assert pytest.approx(call_args['amount'], round(stake_amount / bid * leverage, 8))
|
||||
assert pytest.approx(call_args['amount']) == round(stake_amount / bid * leverage, 8)
|
||||
enter_rate_mock.reset_mock()
|
||||
|
||||
# Should create an open trade with an open order id
|
||||
@@ -813,7 +813,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
call_args = enter_mm.call_args_list[1][1]
|
||||
assert call_args['pair'] == pair
|
||||
assert call_args['rate'] == fix_price
|
||||
assert pytest.approx(call_args['amount'], round(stake_amount / fix_price * leverage, 8))
|
||||
assert pytest.approx(call_args['amount']) == round(stake_amount / fix_price * leverage, 8)
|
||||
|
||||
# In case of closed order
|
||||
order['status'] = 'closed'
|
||||
@@ -2268,6 +2268,7 @@ def test_check_handle_timedout_buy_usercustom(
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
open_trade.is_short = is_short
|
||||
open_trade.orders[0].side = 'sell' if is_short else 'buy'
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# Ensure default is to return empty (so not mocked yet)
|
||||
@@ -2323,6 +2324,7 @@ def test_check_handle_timedout_buy(
|
||||
) -> None:
|
||||
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
old_order['id'] = open_trade.open_order_id
|
||||
limit_buy_cancel = deepcopy(old_order)
|
||||
limit_buy_cancel['status'] = 'canceled'
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
|
||||
@@ -2425,6 +2427,8 @@ def test_check_handle_timedout_sell_usercustom(
|
||||
is_short, open_trade_usdt, caplog
|
||||
) -> None:
|
||||
default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440, "exit_timeout_count": 1}
|
||||
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
|
||||
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
patch_exchange(mocker)
|
||||
@@ -2473,7 +2477,7 @@ def test_check_handle_timedout_sell_usercustom(
|
||||
|
||||
# 2nd canceled trade - Fail execute sell
|
||||
caplog.clear()
|
||||
open_trade_usdt.open_order_id = 'order_id_2'
|
||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||
mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
|
||||
side_effect=DependencyException)
|
||||
@@ -2484,7 +2488,7 @@ def test_check_handle_timedout_sell_usercustom(
|
||||
caplog.clear()
|
||||
|
||||
# 2nd canceled trade ...
|
||||
open_trade_usdt.open_order_id = 'order_id_2'
|
||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||
freqtrade.check_handle_timedout()
|
||||
assert log_has_re('Emergencyselling trade.*', caplog)
|
||||
assert et_mock.call_count == 1
|
||||
@@ -2497,6 +2501,7 @@ def test_check_handle_timedout_sell(
|
||||
) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@@ -2561,6 +2566,7 @@ def test_check_handle_timedout_partial(
|
||||
open_trade, mocker
|
||||
) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
limit_buy_order_old_partial['id'] = open_trade.open_order_id
|
||||
limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
|
||||
limit_buy_canceled['status'] = 'canceled'
|
||||
|
||||
@@ -2594,6 +2600,7 @@ def test_check_handle_timedout_partial_fee(
|
||||
limit_buy_order_old_partial_canceled, mocker
|
||||
) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
limit_buy_order_old_partial['id'] = open_trade.open_order_id
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0))
|
||||
patch_exchange(mocker)
|
||||
@@ -2636,6 +2643,8 @@ def test_check_handle_timedout_partial_except(
|
||||
limit_buy_order_old_partial_canceled, mocker
|
||||
) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id
|
||||
limit_buy_order_old_partial['id'] = open_trade.open_order_id
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@@ -4805,8 +4814,8 @@ def test_get_valid_price(mocker, default_conf_usdt) -> 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),
|
||||
(False, 'spot', 'okx', '', 5.0, 10.0, 1.0, None),
|
||||
(True, 'spot', 'okx', '', 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),
|
||||
@@ -4817,16 +4826,16 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
|
||||
(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
|
||||
# Gateio/okx, 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
|
||||
# Gateio/okx, 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),
|
||||
# (True, 'futures', 'okx', 'isolated', 11.87413417771621),
|
||||
# (False, 'futures', 'okx', 'isolated', 8.085708510208207),
|
||||
]
|
||||
)
|
||||
def test_leverage_prep(
|
||||
@@ -4871,7 +4880,7 @@ def test_leverage_prep(
|
||||
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
|
||||
Gateio/Okx, 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
|
||||
@@ -4882,7 +4891,7 @@ def test_leverage_prep(
|
||||
leverage = 5, open_rate = 8, amount = 1.0
|
||||
(8 + (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 9.499307342172967
|
||||
|
||||
Gateio/Okex, Long
|
||||
Gateio/Okx, 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
|
||||
|
@@ -8,12 +8,13 @@ from unittest.mock import MagicMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, inspect, text
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
|
||||
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
|
||||
from tests.conftest import (create_mock_trades, create_mock_trades_usdt,
|
||||
create_mock_trades_with_leverage, get_sides, log_has, log_has_re)
|
||||
|
||||
@@ -1237,7 +1238,8 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
assert trade.stoploss_last_update is None
|
||||
assert log_has("trying trades_bak1", caplog)
|
||||
assert log_has("trying trades_bak2", caplog)
|
||||
assert log_has("Running database migration for trades - backup: trades_bak2", caplog)
|
||||
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
|
||||
caplog)
|
||||
assert trade.open_trade_value == trade._calc_open_trade_value()
|
||||
assert trade.close_profit_abs is None
|
||||
|
||||
@@ -1250,65 +1252,6 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
assert orders[1].order_id == 'stop_order_id222'
|
||||
assert orders[1].ft_order_side == 'stoploss'
|
||||
|
||||
caplog.clear()
|
||||
# Drop latest column
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text("alter table orders rename to orders_bak"))
|
||||
inspector = inspect(engine)
|
||||
|
||||
with engine.begin() as connection:
|
||||
for index in inspector.get_indexes('orders_bak'):
|
||||
connection.execute(text(f"drop index {index['name']}"))
|
||||
# Recreate table
|
||||
connection.execute(text("""
|
||||
CREATE TABLE orders (
|
||||
id INTEGER NOT NULL,
|
||||
ft_trade_id INTEGER,
|
||||
ft_order_side VARCHAR NOT NULL,
|
||||
ft_pair VARCHAR NOT NULL,
|
||||
ft_is_open BOOLEAN NOT NULL,
|
||||
order_id VARCHAR NOT NULL,
|
||||
status VARCHAR,
|
||||
symbol VARCHAR,
|
||||
order_type VARCHAR,
|
||||
side VARCHAR,
|
||||
price FLOAT,
|
||||
amount FLOAT,
|
||||
filled FLOAT,
|
||||
remaining FLOAT,
|
||||
cost FLOAT,
|
||||
order_date DATETIME,
|
||||
order_filled_date DATETIME,
|
||||
order_update_date DATETIME,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id),
|
||||
FOREIGN KEY(ft_trade_id) REFERENCES trades (id)
|
||||
)
|
||||
"""))
|
||||
|
||||
connection.execute(text("""
|
||||
insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
|
||||
symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
|
||||
order_filled_date, order_update_date)
|
||||
select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
|
||||
symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
|
||||
order_filled_date, order_update_date
|
||||
from orders_bak
|
||||
"""))
|
||||
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
|
||||
assert log_has("trying orders_bak1", caplog)
|
||||
|
||||
orders = Order.query.all()
|
||||
assert len(orders) == 2
|
||||
assert orders[0].order_id == 'buy_order'
|
||||
assert orders[0].ft_order_side == 'buy'
|
||||
|
||||
assert orders[1].order_id == 'stop_order_id222'
|
||||
assert orders[1].ft_order_side == 'stoploss'
|
||||
|
||||
|
||||
def test_migrate_mid_state(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
@@ -1370,7 +1313,40 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.open_trade_value == trade._calc_open_trade_value()
|
||||
assert log_has("trying trades_bak0", caplog)
|
||||
assert log_has("Running database migration for trades - backup: trades_bak0", caplog)
|
||||
assert log_has("Running database migration for trades - backup: trades_bak0, orders_bak0",
|
||||
caplog)
|
||||
|
||||
|
||||
def test_migrate_get_last_sequence_ids():
|
||||
engine = MagicMock()
|
||||
engine.begin = MagicMock()
|
||||
engine.name = 'postgresql'
|
||||
get_last_sequence_ids(engine, 'trades_bak', 'orders_bak')
|
||||
|
||||
assert engine.begin.call_count == 2
|
||||
engine.reset_mock()
|
||||
engine.begin.reset_mock()
|
||||
|
||||
engine.name = 'somethingelse'
|
||||
get_last_sequence_ids(engine, 'trades_bak', 'orders_bak')
|
||||
|
||||
assert engine.begin.call_count == 0
|
||||
|
||||
|
||||
def test_migrate_set_sequence_ids():
|
||||
engine = MagicMock()
|
||||
engine.begin = MagicMock()
|
||||
engine.name = 'postgresql'
|
||||
set_sequence_ids(engine, 22, 55)
|
||||
|
||||
assert engine.begin.call_count == 1
|
||||
engine.reset_mock()
|
||||
engine.begin.reset_mock()
|
||||
|
||||
engine.name = 'somethingelse'
|
||||
set_sequence_ids(engine, 22, 55)
|
||||
|
||||
assert engine.begin.call_count == 0
|
||||
|
||||
|
||||
def test_adjust_stop_loss(fee):
|
||||
@@ -1612,7 +1588,9 @@ def test_to_json(default_conf, fee):
|
||||
'isolated_liq': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None
|
||||
'funding_fees': None,
|
||||
'filled_entry_orders': [],
|
||||
'filled_exit_orders': [],
|
||||
}
|
||||
|
||||
# Simulate dry_run entries
|
||||
@@ -1686,7 +1664,9 @@ def test_to_json(default_conf, fee):
|
||||
'isolated_liq': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None
|
||||
'funding_fees': None,
|
||||
'filled_entry_orders': [],
|
||||
'filled_exit_orders': [],
|
||||
}
|
||||
|
||||
|
||||
|
@@ -189,6 +189,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
|
||||
(9, 11, 100, 10000, 11), # Below min stake
|
||||
(1, 15, 10, 10000, 0), # Below min stake and min_stake > stake_available
|
||||
(20, 50, 100, 10000, 0), # Below min stake and stake * 1.3 > min_stake
|
||||
(1000, None, 1000, 10000, 1000), # No min-stake-amount could be determined
|
||||
|
||||
])
|
||||
def test_validate_stake_amount(
|
||||
|
Reference in New Issue
Block a user