Merge branch 'develop' into backtest_live_models
This commit is contained in:
@@ -18,6 +18,7 @@ from freqtrade.commands import (start_backtesting_show, start_convert_data, star
|
||||
from freqtrade.commands.db_commands import start_convert_db
|
||||
from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui,
|
||||
get_ui_download_url, read_ui_version)
|
||||
from freqtrade.commands.list_commands import start_list_freqAI_models
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
@@ -944,6 +945,34 @@ def test_start_list_strategies(capsys):
|
||||
assert str(Path("broken_strats/broken_futures_strategies.py")) in captured.out
|
||||
|
||||
|
||||
def test_start_list_freqAI_models(capsys):
|
||||
|
||||
args = [
|
||||
"list-freqaimodels",
|
||||
"-1"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_list_freqAI_models(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "LightGBMClassifier" in captured.out
|
||||
assert "LightGBMRegressor" in captured.out
|
||||
assert "XGBoostRegressor" in captured.out
|
||||
assert "<builtin>/LightGBMRegressor.py" not in captured.out
|
||||
|
||||
args = [
|
||||
"list-freqaimodels",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_list_freqAI_models(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "LightGBMClassifier" in captured.out
|
||||
assert "LightGBMRegressor" in captured.out
|
||||
assert "XGBoostRegressor" in captured.out
|
||||
assert "<builtin>/LightGBMRegressor.py" in captured.out
|
||||
|
||||
|
||||
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
patch_exchange(mocker, mock_markets=True)
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
|
@@ -2196,6 +2196,9 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
|
||||
time_machine.move_to(start + timedelta(hours=99, minutes=30))
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
mocker.patch("freqtrade.exchange.Exchange.ohlcv_candle_limit", return_value=100)
|
||||
assert exchange._startup_candle_count == 0
|
||||
|
||||
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
|
||||
pair1 = ('IOTA/ETH', '1h', candle_type)
|
||||
pair2 = ('XRP/ETH', '1h', candle_type)
|
||||
@@ -2236,30 +2239,36 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
|
||||
assert len(res) == 2
|
||||
assert len(res[pair1]) == 99
|
||||
assert len(res[pair2]) == 99
|
||||
assert res[pair2].at[0, 'open']
|
||||
assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000
|
||||
refresh_pior = exchange._pairs_last_refresh_time[pair1]
|
||||
|
||||
# New candle on exchange - only return 50 candles (but one candle further)
|
||||
new_startdate = (start + timedelta(hours=51)).strftime('%Y-%m-%d %H:%M')
|
||||
ohlcv = generate_test_data_raw('1h', 50, new_startdate)
|
||||
# New candle on exchange - return 100 candles - but skip one candle so we actually get 2 candles
|
||||
# in one go
|
||||
new_startdate = (start + timedelta(hours=2)).strftime('%Y-%m-%d %H:%M')
|
||||
# mocker.patch("freqtrade.exchange.Exchange.ohlcv_candle_limit", return_value=100)
|
||||
ohlcv = generate_test_data_raw('1h', 100, new_startdate)
|
||||
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
|
||||
res = exchange.refresh_latest_ohlcv(pairs)
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
||||
assert len(res) == 2
|
||||
assert len(res[pair1]) == 100
|
||||
assert len(res[pair2]) == 100
|
||||
# Verify index starts at 0
|
||||
assert res[pair2].at[0, 'open']
|
||||
assert refresh_pior != exchange._pairs_last_refresh_time[pair1]
|
||||
|
||||
assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000
|
||||
assert exchange._pairs_last_refresh_time[pair2] == ohlcv[-1][0] // 1000
|
||||
exchange._api_async.fetch_ohlcv.reset_mock()
|
||||
|
||||
# Retry same call - no action.
|
||||
# Retry same call - from cache
|
||||
res = exchange.refresh_latest_ohlcv(pairs)
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == 0
|
||||
assert len(res) == 2
|
||||
assert len(res[pair1]) == 100
|
||||
assert len(res[pair2]) == 100
|
||||
assert res[pair2].at[0, 'open']
|
||||
|
||||
# Move to distant future (so a 1 call would cause a hole in the data)
|
||||
time_machine.move_to(start + timedelta(hours=2000))
|
||||
@@ -2272,6 +2281,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
|
||||
# Cache eviction - new data.
|
||||
assert len(res[pair1]) == 99
|
||||
assert len(res[pair2]) == 99
|
||||
assert res[pair2].at[0, 'open']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -4341,9 +4351,10 @@ def test__fetch_and_calculate_funding_fees_datetime_called(
|
||||
('XLTCUSDT', 1, 'spot'),
|
||||
('LTC/USD', 1, 'futures'),
|
||||
('XLTCUSDT', 0.01, 'futures'),
|
||||
('ETH/USDT:USDT', 10, 'futures')
|
||||
('ETH/USDT:USDT', 10, 'futures'),
|
||||
('TORN/USDT:USDT', None, 'futures'), # Don't fail for unavailable pairs.
|
||||
])
|
||||
def est__get_contract_size(mocker, default_conf, pair, expected_size, trading_mode):
|
||||
def test__get_contract_size(mocker, default_conf, pair, expected_size, trading_mode):
|
||||
api_mock = MagicMock()
|
||||
default_conf['trading_mode'] = trading_mode
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
|
@@ -30,6 +30,7 @@ def is_mac() -> bool:
|
||||
@pytest.mark.parametrize('model', [
|
||||
'LightGBMRegressor',
|
||||
'XGBoostRegressor',
|
||||
'XGBoostRFRegressor',
|
||||
'CatboostRegressor',
|
||||
])
|
||||
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model):
|
||||
@@ -55,10 +56,17 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model):
|
||||
|
||||
data_load_timerange = TimeRange.parse_timerange("20180125-20180130")
|
||||
new_timerange = TimeRange.parse_timerange("20180127-20180130")
|
||||
freqai.dk.set_paths('ADA/BTC', None)
|
||||
|
||||
freqai.train_timer("start", "ADA/BTC")
|
||||
freqai.extract_data_and_train_model(
|
||||
new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange)
|
||||
freqai.train_timer("stop", "ADA/BTC")
|
||||
freqai.dd.save_metric_tracker_to_disk()
|
||||
freqai.dd.save_drawer_to_disk()
|
||||
|
||||
assert Path(freqai.dk.full_path / "metric_tracker.json").is_file()
|
||||
assert Path(freqai.dk.full_path / "pair_dictionary.json").is_file()
|
||||
assert Path(freqai.dk.data_path /
|
||||
f"{freqai.dk.model_filename}_model.{model_save_ext}").is_file()
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").is_file()
|
||||
@@ -93,6 +101,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model):
|
||||
|
||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
new_timerange = TimeRange.parse_timerange("20180120-20180130")
|
||||
freqai.dk.set_paths('ADA/BTC', None)
|
||||
|
||||
freqai.extract_data_and_train_model(
|
||||
new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange)
|
||||
@@ -111,6 +120,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model):
|
||||
'LightGBMClassifier',
|
||||
'CatboostClassifier',
|
||||
'XGBoostClassifier',
|
||||
'XGBoostRFClassifier',
|
||||
])
|
||||
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
if is_arm() and model == 'CatboostClassifier':
|
||||
@@ -134,6 +144,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
|
||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
new_timerange = TimeRange.parse_timerange("20180120-20180130")
|
||||
freqai.dk.set_paths('ADA/BTC', None)
|
||||
|
||||
freqai.extract_data_and_train_model(new_timerange, "ADA/BTC",
|
||||
strategy, freqai.dk, data_load_timerange)
|
||||
|
@@ -97,7 +97,6 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
'max_open_trades': 10,
|
||||
'position_stacking': False,
|
||||
}
|
||||
|
||||
|
||||
@@ -735,7 +734,6 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=10,
|
||||
position_stacking=False,
|
||||
)
|
||||
results = result['results']
|
||||
assert not results.empty
|
||||
@@ -799,6 +797,34 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
|
||||
|
||||
|
||||
def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) -> None:
|
||||
# This strategy intentionally places unfillable orders.
|
||||
default_conf['strategy'] = 'StrategyTestV3CustomEntryPrice'
|
||||
default_conf['startup_candle_count'] = 0
|
||||
# Cancel unfilled order after 4 minutes on 5m timeframe.
|
||||
default_conf["unfilledtimeout"] = {"entry": 4}
|
||||
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=float('inf'))
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
# Testing dataframe contains 11 candles. Expecting 10 timed out orders.
|
||||
timerange = TimeRange('date', 'date', 1517227800, 1517231100)
|
||||
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
|
||||
timerange=timerange)
|
||||
min_date, max_date = get_timerange(data)
|
||||
|
||||
result = backtesting.backtest(
|
||||
processed=deepcopy(data),
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
)
|
||||
|
||||
assert result['timedout_entry_orders'] == 10
|
||||
|
||||
|
||||
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['use_exit_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
@@ -819,7 +845,6 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
position_stacking=False,
|
||||
)
|
||||
assert not results['results'].empty
|
||||
assert len(results['results']) == 1
|
||||
@@ -851,7 +876,6 @@ def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> N
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=10,
|
||||
position_stacking=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -906,7 +930,6 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=10,
|
||||
position_stacking=False,
|
||||
)
|
||||
assert count == 5
|
||||
|
||||
@@ -950,8 +973,6 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
position_stacking=False,
|
||||
enable_protections=default_conf.get('enable_protections', False),
|
||||
)
|
||||
assert len(results['results']) == numres
|
||||
|
||||
@@ -994,8 +1015,6 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
position_stacking=False,
|
||||
enable_protections=default_conf.get('enable_protections', False),
|
||||
)
|
||||
assert len(results['results']) == expected
|
||||
|
||||
@@ -1107,7 +1126,6 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
'max_open_trades': 3,
|
||||
'position_stacking': False,
|
||||
}
|
||||
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
@@ -1130,7 +1148,6 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
'max_open_trades': 1,
|
||||
'position_stacking': False,
|
||||
}
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0
|
||||
|
@@ -42,7 +42,6 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=10,
|
||||
position_stacking=False,
|
||||
)
|
||||
results = result['results']
|
||||
assert not results.empty
|
||||
|
@@ -336,7 +336,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
|
||||
assert hasattr(hyperopt, "max_open_trades")
|
||||
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
|
||||
assert hasattr(hyperopt, "position_stacking")
|
||||
assert hasattr(hyperopt.backtesting, "_position_stacking")
|
||||
|
||||
|
||||
def test_hyperopt_format_results(hyperopt):
|
||||
@@ -704,7 +704,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
|
||||
assert hasattr(hyperopt, "max_open_trades")
|
||||
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
|
||||
assert hasattr(hyperopt, "position_stacking")
|
||||
assert hasattr(hyperopt.backtesting, "_position_stacking")
|
||||
|
||||
|
||||
def test_simplified_interface_all_failed(mocker, hyperopt_conf, caplog) -> None:
|
||||
@@ -778,7 +778,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
|
||||
assert hasattr(hyperopt, "max_open_trades")
|
||||
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
|
||||
assert hasattr(hyperopt, "position_stacking")
|
||||
assert hasattr(hyperopt.backtesting, "_position_stacking")
|
||||
|
||||
|
||||
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
@@ -821,7 +821,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
|
||||
assert hasattr(hyperopt, "max_open_trades")
|
||||
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
|
||||
assert hasattr(hyperopt, "position_stacking")
|
||||
assert hasattr(hyperopt.backtesting, "_position_stacking")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("space", [
|
||||
|
@@ -1443,8 +1443,9 @@ def test_api_plot_config(botclient):
|
||||
assert isinstance(rc.json()['subplots'], dict)
|
||||
|
||||
|
||||
def test_api_strategies(botclient):
|
||||
def test_api_strategies(botclient, tmpdir):
|
||||
ftbot, client = botclient
|
||||
ftbot.config['user_data_dir'] = Path(tmpdir)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/strategies")
|
||||
|
||||
@@ -1456,6 +1457,7 @@ def test_api_strategies(botclient):
|
||||
'InformativeDecoratorTest',
|
||||
'StrategyTestV2',
|
||||
'StrategyTestV3',
|
||||
'StrategyTestV3CustomEntryPrice',
|
||||
'StrategyTestV3Futures',
|
||||
'freqai_test_classifier',
|
||||
'freqai_test_multimodel_strat',
|
||||
|
37
tests/strategy/strats/strategy_test_v3_custom_entry_price.py
Normal file
37
tests/strategy/strats/strategy_test_v3_custom_entry_price.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pandas import DataFrame
|
||||
from strategy_test_v3 import StrategyTestV3
|
||||
|
||||
|
||||
class StrategyTestV3CustomEntryPrice(StrategyTestV3):
|
||||
"""
|
||||
Strategy used by tests freqtrade bot.
|
||||
Please do not modify this strategy, it's intended for internal use only.
|
||||
Please look at the SampleStrategy in the user_data/strategy directory
|
||||
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
||||
for samples and inspiration.
|
||||
"""
|
||||
new_entry_price: float = 0.001
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
|
||||
dataframe.loc[
|
||||
dataframe['volume'] > 0,
|
||||
'enter_long'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
return dataframe
|
||||
|
||||
def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
|
||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
||||
|
||||
return self.new_entry_price
|
@@ -32,24 +32,25 @@ def test_search_strategy():
|
||||
|
||||
def test_search_all_strategies_no_failed():
|
||||
directory = Path(__file__).parent / "strats"
|
||||
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
|
||||
strategies = StrategyResolver._search_all_objects(directory, enum_failed=False)
|
||||
assert isinstance(strategies, list)
|
||||
assert len(strategies) == 9
|
||||
assert len(strategies) == 10
|
||||
assert isinstance(strategies[0], dict)
|
||||
|
||||
|
||||
def test_search_all_strategies_with_failed():
|
||||
directory = Path(__file__).parent / "strats"
|
||||
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
|
||||
strategies = StrategyResolver._search_all_objects(directory, enum_failed=True)
|
||||
assert isinstance(strategies, list)
|
||||
assert len(strategies) == 10
|
||||
assert len(strategies) == 11
|
||||
# with enum_failed=True search_all_objects() shall find 2 good strategies
|
||||
# and 1 which fails to load
|
||||
assert len([x for x in strategies if x['class'] is not None]) == 9
|
||||
assert len([x for x in strategies if x['class'] is not None]) == 10
|
||||
|
||||
assert len([x for x in strategies if x['class'] is None]) == 1
|
||||
|
||||
directory = Path(__file__).parent / "strats_nonexistingdir"
|
||||
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
|
||||
strategies = StrategyResolver._search_all_objects(directory, enum_failed=True)
|
||||
assert len(strategies) == 0
|
||||
|
||||
|
||||
@@ -77,10 +78,9 @@ def test_load_strategy_base64(dataframe_1m, caplog, default_conf):
|
||||
|
||||
|
||||
def test_load_strategy_invalid_directory(caplog, default_conf):
|
||||
default_conf['strategy'] = 'StrategyTestV3'
|
||||
extra_dir = Path.cwd() / 'some/path'
|
||||
with pytest.raises(OperationalException):
|
||||
StrategyResolver._load_strategy(CURRENT_TEST_STRATEGY, config=default_conf,
|
||||
with pytest.raises(OperationalException, match=r"Impossible to load Strategy.*"):
|
||||
StrategyResolver._load_strategy('StrategyTestV333', config=default_conf,
|
||||
extra_dir=extra_dir)
|
||||
|
||||
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
|
||||
@@ -102,8 +102,8 @@ def test_load_strategy_noname(default_conf):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
@pytest.mark.parametrize('strategy_name', ['StrategyTestV2'])
|
||||
@ pytest.mark.filterwarnings("ignore:deprecated")
|
||||
@ pytest.mark.parametrize('strategy_name', ['StrategyTestV2'])
|
||||
def test_strategy_pre_v3(dataframe_1m, default_conf, strategy_name):
|
||||
default_conf.update({'strategy': strategy_name})
|
||||
|
||||
@@ -349,7 +349,7 @@ def test_strategy_override_use_exit_profit_only(caplog, default_conf):
|
||||
assert log_has("Override strategy 'exit_profit_only' with value in config file: True.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
@ pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_missing_implements(default_conf, caplog):
|
||||
|
||||
default_location = Path(__file__).parent / "strats"
|
||||
|
@@ -2406,6 +2406,8 @@ def test_Trade_object_idem():
|
||||
'get_trading_volume',
|
||||
|
||||
)
|
||||
EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count',
|
||||
'total_profit')
|
||||
|
||||
# Parent (LocalTrade) should have the same attributes
|
||||
for item in trade:
|
||||
@@ -2416,7 +2418,7 @@ def test_Trade_object_idem():
|
||||
# Fails if only a column is added without corresponding parent field
|
||||
for item in localtrade:
|
||||
if (not item.startswith('__')
|
||||
and item not in ('trades', 'trades_open', 'total_profit')
|
||||
and item not in EXCLUDES2
|
||||
and type(getattr(LocalTrade, item)) not in (property, FunctionType)):
|
||||
assert item in trade
|
||||
|
||||
|
Reference in New Issue
Block a user