Merge branch 'develop' into dataformat/feather
This commit is contained in:
+16
-16
@@ -3002,85 +3002,85 @@ def mark_ohlcv():
|
||||
def funding_rate_history_hourly():
|
||||
return [
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": -0.000008,
|
||||
"timestamp": 1630454400000,
|
||||
"datetime": "2021-09-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": -0.000004,
|
||||
"timestamp": 1630458000000,
|
||||
"datetime": "2021-09-01T01:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0.000012,
|
||||
"timestamp": 1630461600000,
|
||||
"datetime": "2021-09-01T02:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": -0.000003,
|
||||
"timestamp": 1630465200000,
|
||||
"datetime": "2021-09-01T03:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": -0.000007,
|
||||
"timestamp": 1630468800000,
|
||||
"datetime": "2021-09-01T04:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0.000003,
|
||||
"timestamp": 1630472400000,
|
||||
"datetime": "2021-09-01T05:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0.000019,
|
||||
"timestamp": 1630476000000,
|
||||
"datetime": "2021-09-01T06:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0.000003,
|
||||
"timestamp": 1630479600000,
|
||||
"datetime": "2021-09-01T07:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": -0.000003,
|
||||
"timestamp": 1630483200000,
|
||||
"datetime": "2021-09-01T08:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0,
|
||||
"timestamp": 1630486800000,
|
||||
"datetime": "2021-09-01T09:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0.000013,
|
||||
"timestamp": 1630490400000,
|
||||
"datetime": "2021-09-01T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0.000077,
|
||||
"timestamp": 1630494000000,
|
||||
"datetime": "2021-09-01T11:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0.000072,
|
||||
"timestamp": 1630497600000,
|
||||
"datetime": "2021-09-01T12:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": 0.000097,
|
||||
"timestamp": 1630501200000,
|
||||
"datetime": "2021-09-01T13:00:00.000Z"
|
||||
@@ -3092,13 +3092,13 @@ def funding_rate_history_hourly():
|
||||
def funding_rate_history_octohourly():
|
||||
return [
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": -0.000008,
|
||||
"timestamp": 1630454400000,
|
||||
"datetime": "2021-09-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"symbol": "ADA/USDT",
|
||||
"symbol": "ADA/USDT:USDT",
|
||||
"fundingRate": -0.000003,
|
||||
"timestamp": 1630483200000,
|
||||
"datetime": "2021-09-01T08:00:00.000Z"
|
||||
|
||||
@@ -343,12 +343,24 @@ def test_calculate_expectancy(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
|
||||
expectancy = calculate_expectancy(DataFrame())
|
||||
expectancy, expectancy_ratio = calculate_expectancy(DataFrame())
|
||||
assert expectancy == 0.0
|
||||
assert expectancy_ratio == 100
|
||||
|
||||
expectancy = calculate_expectancy(bt_data)
|
||||
expectancy, expectancy_ratio = calculate_expectancy(bt_data)
|
||||
assert isinstance(expectancy, float)
|
||||
assert pytest.approx(expectancy) == 0.07151374226574791
|
||||
assert isinstance(expectancy_ratio, float)
|
||||
assert pytest.approx(expectancy) == 5.820687070932315e-06
|
||||
assert pytest.approx(expectancy_ratio) == 0.07151374226574791
|
||||
|
||||
data = {
|
||||
'profit_abs': [100, 200, 50, -150, 300, -100, 80, -30]
|
||||
}
|
||||
df = DataFrame(data)
|
||||
expectancy, expectancy_ratio = calculate_expectancy(df)
|
||||
|
||||
assert pytest.approx(expectancy) == 56.25
|
||||
assert pytest.approx(expectancy_ratio) == 0.60267857
|
||||
|
||||
|
||||
def test_calculate_sortino(testdatadir):
|
||||
|
||||
@@ -135,6 +135,73 @@ def test_ohlcv_fill_up_missing_data2(caplog):
|
||||
f"{len(data)} - after: {len(data2)}.*", caplog)
|
||||
|
||||
|
||||
def test_ohlcv_to_dataframe_1M():
|
||||
|
||||
# Monthly ticks from 2019-09-01 to 2023-07-01
|
||||
ticks = [
|
||||
[1567296000000, 8042.08, 10475.54, 7700.67, 8041.96, 608742.1109999999],
|
||||
[1569888000000, 8285.31, 10408.48, 7172.76, 9150.0, 2439561.887],
|
||||
[1572566400000, 9149.88, 9550.0, 6510.19, 7542.93, 4042674.725],
|
||||
[1575158400000, 7541.08, 7800.0, 6427.0, 7189.0, 4063882.296],
|
||||
[1577836800000, 7189.43, 9599.0, 6863.44, 9364.51, 5165281.358],
|
||||
[1580515200000, 9364.5, 10540.0, 8450.0, 8531.98, 4581788.124],
|
||||
[1583020800000, 8532.5, 9204.0, 3621.81, 6407.1, 10859497.479],
|
||||
[1585699200000, 6407.1, 9479.77, 6140.0, 8624.76, 11276526.968],
|
||||
[1588291200000, 8623.61, 10080.0, 7940.0, 9446.43, 12469561.02],
|
||||
[1590969600000, 9446.49, 10497.25, 8816.4, 9138.87, 6684044.201],
|
||||
[1593561600000, 9138.88, 11488.0, 8900.0, 11343.68, 5709327.926],
|
||||
[1596240000000, 11343.67, 12499.42, 10490.0, 11658.11, 6746487.129],
|
||||
[1598918400000, 11658.11, 12061.07, 9808.58, 10773.0, 6442697.051],
|
||||
[1601510400000, 10773.0, 14140.0, 10371.03, 13783.73, 7404103.004],
|
||||
[1604188800000, 13783.73, 19944.0, 13195.0, 19720.0, 12328272.549],
|
||||
[1606780800000, 19722.09, 29376.7, 17555.0, 28951.68, 10067314.24],
|
||||
[1609459200000, 28948.19, 42125.51, 27800.0, 33126.21, 12408873.079],
|
||||
[1612137600000, 33125.11, 58472.14, 32322.47, 45163.36, 8784474.482],
|
||||
[1614556800000, 45162.64, 61950.0, 44972.49, 58807.24, 9459821.267],
|
||||
[1617235200000, 58810.99, 64986.11, 46930.43, 57684.16, 7895051.389],
|
||||
[1619827200000, 57688.29, 59654.0, 28688.0, 37243.38, 16790964.443],
|
||||
[1622505600000, 37244.36, 41413.0, 28780.01, 35031.39, 23474519.886],
|
||||
[1625097600000, 35031.39, 48168.6, 29242.24, 41448.11, 16932491.175],
|
||||
[1627776000000, 41448.1, 50600.0, 37291.0, 47150.32, 13645800.254],
|
||||
[1630454400000, 47150.32, 52950.0, 39503.58, 43796.57, 10734742.869],
|
||||
[1633046400000, 43799.49, 67150.0, 43260.01, 61348.61, 9111112.847],
|
||||
[1635724800000, 61347.14, 69198.7, 53245.0, 56975.0, 7111424.463],
|
||||
[1638316800000, 56978.06, 59100.0, 40888.89, 46210.56, 8404449.024],
|
||||
[1640995200000, 46210.57, 48000.0, 32853.83, 38439.04, 11047479.277],
|
||||
[1643673600000, 38439.04, 45847.5, 34303.7, 43155.0, 10910339.91],
|
||||
[1646092800000, 43155.0, 48200.0, 37134.0, 45506.0, 10459721.586],
|
||||
[1648771200000, 45505.9, 47448.0, 37550.0, 37614.5, 8463568.862],
|
||||
[1651363200000, 37614.4, 40071.7, 26631.0, 31797.8, 14463715.774],
|
||||
[1654041600000, 31797.9, 31986.1, 17593.2, 19923.5, 20710810.306],
|
||||
[1656633600000, 19923.3, 24700.0, 18780.1, 23290.1, 20582518.513],
|
||||
[1659312000000, 23290.1, 25200.0, 19508.0, 20041.5, 17221921.557],
|
||||
[1661990400000, 20041.4, 22850.0, 18084.3, 19411.7, 21935261.414],
|
||||
[1664582400000, 19411.6, 21088.0, 17917.8, 20482.0, 16625843.584],
|
||||
[1667260800000, 20482.1, 21473.7, 15443.2, 17153.3, 18460614.013],
|
||||
[1669852800000, 17153.4, 18400.0, 16210.0, 16537.6, 9702408.711],
|
||||
[1672531200000, 16537.5, 23962.7, 16488.0, 23119.4, 14732180.645],
|
||||
[1675209600000, 23119.5, 25347.6, 21338.0, 23129.6, 15025197.415],
|
||||
[1677628800000, 23129.7, 29184.8, 19521.6, 28454.9, 23317458.541],
|
||||
[1680307200000, 28454.8, 31059.0, 26919.3, 29223.0, 14654208.219],
|
||||
[1682899200000, 29223.0, 29840.0, 25751.0, 27201.1, 13328157.284],
|
||||
[1685577600000, 27201.1, 31500.0, 24777.0, 30460.2, 14099299.273],
|
||||
[1688169600000, 30460.2, 31850.0, 28830.0, 29338.8, 8760361.377]
|
||||
]
|
||||
|
||||
data = ohlcv_to_dataframe(ticks, '1M', pair="UNITTEST/USDT",
|
||||
fill_missing=False, drop_incomplete=False)
|
||||
assert len(data) == len(ticks)
|
||||
assert data.iloc[0]['date'].strftime('%Y-%m-%d') == '2019-09-01'
|
||||
assert data.iloc[-1]['date'].strftime('%Y-%m-%d') == '2023-07-01'
|
||||
|
||||
# Test with filling missing data
|
||||
data = ohlcv_to_dataframe(ticks, '1M', pair="UNITTEST/USDT",
|
||||
fill_missing=True, drop_incomplete=False)
|
||||
assert len(data) == len(ticks)
|
||||
assert data.iloc[0]['date'].strftime('%Y-%m-%d') == '2019-09-01'
|
||||
assert data.iloc[-1]['date'].strftime('%Y-%m-%d') == '2023-07-01'
|
||||
|
||||
|
||||
def test_ohlcv_drop_incomplete(caplog):
|
||||
timeframe = '1d'
|
||||
ticks = [
|
||||
|
||||
@@ -544,6 +544,8 @@ class TestCCXTExchange:
|
||||
if exchangename in ('bittrex'):
|
||||
# For some weired reason, this test returns random lengths for bittrex.
|
||||
pytest.skip("Exchange doesn't provide stable ohlcv history")
|
||||
if exchangename in ('bitvavo'):
|
||||
pytest.skip("Exchange Downtime ")
|
||||
|
||||
if not exc._ft_has['ohlcv_has_history']:
|
||||
pytest.skip("Exchange does not support candle history")
|
||||
|
||||
@@ -4343,11 +4343,11 @@ def test__fetch_and_calculate_funding_fees(
|
||||
ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
|
||||
mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['1h', '4h', '8h']))
|
||||
funding_fees = ex._fetch_and_calculate_funding_fees(
|
||||
pair='ADA/USDT', amount=amount, is_short=True, open_date=d1, close_date=d2)
|
||||
pair='ADA/USDT:USDT', amount=amount, is_short=True, open_date=d1, close_date=d2)
|
||||
assert pytest.approx(funding_fees) == expected_fees
|
||||
# Fees for Longs are inverted
|
||||
funding_fees = ex._fetch_and_calculate_funding_fees(
|
||||
pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
|
||||
pair='ADA/USDT:USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
|
||||
assert pytest.approx(funding_fees) == -expected_fees
|
||||
|
||||
# Return empty "refresh_latest"
|
||||
@@ -4355,7 +4355,7 @@ def test__fetch_and_calculate_funding_fees(
|
||||
ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
|
||||
with pytest.raises(ExchangeError, match="Could not find funding rates."):
|
||||
ex._fetch_and_calculate_funding_fees(
|
||||
pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
|
||||
pair='ADA/USDT:USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,expected_fees', [
|
||||
@@ -5424,7 +5424,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
|
||||
|
||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == order_amount
|
||||
assert order['amount'] == 100
|
||||
assert order['cost'] == 100
|
||||
assert order['cost'] == order_amount
|
||||
assert order['filled'] == 100
|
||||
assert order['remaining'] == 100
|
||||
|
||||
|
||||
@@ -601,6 +601,9 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='short')
|
||||
assert pytest.approx(trade.liquidation_price) == 0.11787191
|
||||
assert pytest.approx(trade.orders[0].cost) == (
|
||||
trade.stake_amount * trade.leverage + trade.fee_open)
|
||||
assert pytest.approx(trade.orders[-1].stake_amount) == trade.stake_amount
|
||||
|
||||
# Stake-amount too high!
|
||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=600.0)
|
||||
|
||||
@@ -23,7 +23,8 @@ from freqtrade.optimize.optimize_reports import (generate_backtest_stats, genera
|
||||
store_backtest_analysis_results,
|
||||
store_backtest_stats, text_table_bt_results,
|
||||
text_table_exit_reason, text_table_strategy)
|
||||
from freqtrade.optimize.optimize_reports.optimize_reports import _get_resample_from_period
|
||||
from freqtrade.optimize.optimize_reports.optimize_reports import (_get_resample_from_period,
|
||||
calc_streak)
|
||||
from freqtrade.resolvers.strategy_resolver import StrategyResolver
|
||||
from freqtrade.util import dt_ts
|
||||
from freqtrade.util.datetime_helpers import dt_from_ts, dt_utc
|
||||
@@ -348,6 +349,32 @@ def test_generate_trading_stats(testdatadir):
|
||||
assert res['losses'] == 0
|
||||
|
||||
|
||||
def test_calc_streak(testdatadir):
|
||||
df = pd.DataFrame({
|
||||
'profit_ratio': [0.05, -0.02, -0.03, -0.05, 0.01, 0.02, 0.03, 0.04, -0.02, -0.03],
|
||||
})
|
||||
# 4 consecutive wins, 3 consecutive losses
|
||||
res = calc_streak(df)
|
||||
assert res == (4, 3)
|
||||
assert isinstance(res[0], int)
|
||||
assert isinstance(res[1], int)
|
||||
|
||||
# invert situation
|
||||
df1 = df.copy()
|
||||
df1['profit_ratio'] = df1['profit_ratio'] * -1
|
||||
assert calc_streak(df1) == (3, 4)
|
||||
|
||||
df_empty = pd.DataFrame({
|
||||
'profit_ratio': [],
|
||||
})
|
||||
assert df_empty.empty
|
||||
assert calc_streak(df_empty) == (0, 0)
|
||||
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
assert calc_streak(bt_data) == (7, 18)
|
||||
|
||||
|
||||
def test_text_table_exit_reason():
|
||||
|
||||
results = pd.DataFrame(
|
||||
|
||||
@@ -563,14 +563,14 @@ def test_calc_open_close_trade_price(
|
||||
trade.open_order_id = f'something-{is_short}-{lev}-{exchange}'
|
||||
|
||||
oobj = Order.parse_from_ccxt_object(entry_order, 'ADA/USDT', trade.entry_side)
|
||||
oobj.trade = trade
|
||||
oobj._trade_live = trade
|
||||
oobj.update_from_ccxt_object(entry_order)
|
||||
trade.update_trade(oobj)
|
||||
|
||||
trade.funding_fees = funding_fees
|
||||
|
||||
oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', trade.exit_side)
|
||||
oobj.trade = trade
|
||||
oobj._trade_live = trade
|
||||
oobj.update_from_ccxt_object(exit_order)
|
||||
trade.update_trade(oobj)
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ def test_trade_fromjson():
|
||||
assert trade.open_date_utc == datetime(2022, 10, 18, 9, 12, 42, tzinfo=timezone.utc)
|
||||
assert isinstance(trade.open_date, datetime)
|
||||
assert trade.exit_reason == 'no longer good'
|
||||
assert trade.realized_profit == 2.76315361
|
||||
|
||||
assert len(trade.orders) == 5
|
||||
last_o = trade.orders[-1]
|
||||
|
||||
@@ -35,7 +35,7 @@ def test_gen_pairlist_with_local_file(mocker, rpl_config):
|
||||
mock_file_path.exists.return_value = True
|
||||
|
||||
jsonparse = json.loads(mock_file.read.return_value)
|
||||
mocker.patch('freqtrade.plugins.pairlist.RemotePairList.json.load', return_value=jsonparse)
|
||||
mocker.patch('freqtrade.plugins.pairlist.RemotePairList.rapidjson.load', return_value=jsonparse)
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
{
|
||||
|
||||
@@ -402,6 +402,8 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
|
||||
assert res['first_trade_timestamp'] == 0
|
||||
assert res['latest_trade_date'] == ''
|
||||
assert res['latest_trade_timestamp'] == 0
|
||||
assert res['expectancy'] == 0
|
||||
assert res['expectancy_ratio'] == 100
|
||||
|
||||
# Create some test data
|
||||
create_mock_trades_usdt(fee)
|
||||
@@ -413,6 +415,9 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
|
||||
assert pytest.approx(stats['profit_all_coin']) == -77.45964918
|
||||
assert pytest.approx(stats['profit_all_percent_mean']) == -57.86
|
||||
assert pytest.approx(stats['profit_all_fiat']) == -85.205614098
|
||||
assert pytest.approx(stats['winrate']) == 0.666666667
|
||||
assert pytest.approx(stats['expectancy']) == 0.913333333
|
||||
assert pytest.approx(stats['expectancy_ratio']) == 0.223308883
|
||||
assert stats['trade_count'] == 7
|
||||
assert stats['first_trade_humanized'] == '2 days ago'
|
||||
assert stats['latest_trade_humanized'] == '17 minutes ago'
|
||||
|
||||
@@ -829,7 +829,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015,
|
||||
'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06,
|
||||
'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2,
|
||||
'profit_factor': 0.0, 'trading_volume': 91.074,
|
||||
'profit_factor': 0.0, 'winrate': 0.0, 'expectancy': -0.0033695635,
|
||||
'expectancy_ratio': -1.0, 'trading_volume': 91.074,
|
||||
}
|
||||
),
|
||||
(
|
||||
@@ -844,7 +845,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015,
|
||||
'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07,
|
||||
'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0,
|
||||
'profit_factor': None, 'trading_volume': 91.074,
|
||||
'profit_factor': None, 'winrate': 1.0, 'expectancy': 0.0003695635,
|
||||
'expectancy_ratio': 100, 'trading_volume': 91.074,
|
||||
}
|
||||
),
|
||||
(
|
||||
@@ -859,7 +861,9 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005,
|
||||
'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06,
|
||||
'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1,
|
||||
'profit_factor': 0.02775724835771106, 'trading_volume': 91.074,
|
||||
'profit_factor': 0.02775724835771106, 'winrate': 0.5,
|
||||
'expectancy': -0.0027145635000000003, 'expectancy_ratio': -0.48612137582114445,
|
||||
'trading_volume': 91.074,
|
||||
}
|
||||
)
|
||||
])
|
||||
@@ -916,6 +920,9 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
|
||||
'winning_trades': expected['winning_trades'],
|
||||
'losing_trades': expected['losing_trades'],
|
||||
'profit_factor': expected['profit_factor'],
|
||||
'winrate': expected['winrate'],
|
||||
'expectancy': expected['expectancy'],
|
||||
'expectancy_ratio': expected['expectancy_ratio'],
|
||||
'max_drawdown': ANY,
|
||||
'max_drawdown_abs': ANY,
|
||||
'trading_volume': expected['trading_volume'],
|
||||
@@ -1469,30 +1476,47 @@ def test_api_pair_history(botclient, mocker):
|
||||
"&timerange=20180111-20180112")
|
||||
assert_response(rc, 422)
|
||||
|
||||
# Invalid strategy
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||
"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}11")
|
||||
assert_response(rc, 502)
|
||||
|
||||
# Working
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
|
||||
assert_response(rc, 200)
|
||||
assert rc.json()['length'] == 289
|
||||
assert len(rc.json()['data']) == rc.json()['length']
|
||||
assert 'columns' in rc.json()
|
||||
assert 'data' in rc.json()
|
||||
result = rc.json()
|
||||
assert result['length'] == 289
|
||||
assert len(result['data']) == result['length']
|
||||
assert 'columns' in result
|
||||
assert 'data' in result
|
||||
data = result['data']
|
||||
assert len(data) == 289
|
||||
# analyed DF has 28 columns
|
||||
assert len(result['columns']) == 28
|
||||
assert len(data[0]) == 28
|
||||
date_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'date'][0]
|
||||
rsi_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'rsi'][0]
|
||||
|
||||
assert data[0][date_col_idx] == '2018-01-11 00:00:00'
|
||||
assert data[0][rsi_col_idx] is not None
|
||||
assert data[0][rsi_col_idx] > 0
|
||||
assert lfm.call_count == 1
|
||||
assert rc.json()['pair'] == 'UNITTEST/BTC'
|
||||
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
|
||||
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
|
||||
assert rc.json()['data_start_ts'] == 1515628800000
|
||||
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
|
||||
assert rc.json()['data_stop_ts'] == 1515715200000
|
||||
assert result['pair'] == 'UNITTEST/BTC'
|
||||
assert result['strategy'] == CURRENT_TEST_STRATEGY
|
||||
assert result['data_start'] == '2018-01-11 00:00:00+00:00'
|
||||
assert result['data_start_ts'] == 1515628800000
|
||||
assert result['data_stop'] == '2018-01-12 00:00:00+00:00'
|
||||
assert result['data_stop_ts'] == 1515715200000
|
||||
|
||||
# No data found
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||
f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}")
|
||||
assert_response(rc, 502)
|
||||
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
|
||||
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
|
||||
assert rc.json()['detail'] == ("No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
|
||||
|
||||
|
||||
def test_api_plot_config(botclient, mocker):
|
||||
@@ -1529,6 +1553,10 @@ def test_api_plot_config(botclient, mocker):
|
||||
assert_response(rc)
|
||||
assert rc.json()['subplots'] == {}
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/plot_config?strategy=NotAStrategy")
|
||||
assert_response(rc, 502)
|
||||
assert rc.json()['detail'] is not None
|
||||
|
||||
mocker.patch('freqtrade.rpc.api_server.api_v1.get_rpc_optional', return_value=None)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/plot_config")
|
||||
@@ -1981,7 +2009,7 @@ def test_api_backtest_history(botclient, mocker, testdatadir):
|
||||
result = rc.json()
|
||||
assert len(result) == 3
|
||||
fn = result[0]['filename']
|
||||
assert fn == "backtest-result_multistrat.json"
|
||||
assert fn == "backtest-result_multistrat"
|
||||
strategy = result[0]['strategy']
|
||||
rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}&strategy={strategy}")
|
||||
assert_response(rc)
|
||||
@@ -1995,6 +2023,34 @@ def test_api_backtest_history(botclient, mocker, testdatadir):
|
||||
assert result2['backtest_result']['strategy'][strategy]
|
||||
|
||||
|
||||
def test_api_delete_backtest_history_entry(botclient, mocker, tmp_path: Path):
|
||||
ftbot, client = botclient
|
||||
|
||||
# Create a temporary directory and file
|
||||
bt_results_base = tmp_path / "backtest_results"
|
||||
bt_results_base.mkdir()
|
||||
file_path = bt_results_base / "test.json"
|
||||
file_path.touch()
|
||||
meta_path = file_path.with_suffix('.meta.json')
|
||||
meta_path.touch()
|
||||
|
||||
rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json")
|
||||
assert_response(rc, 503)
|
||||
assert rc.json()['detail'] == 'Bot is not in the correct state.'
|
||||
|
||||
ftbot.config['user_data_dir'] = tmp_path
|
||||
ftbot.config['runmode'] = RunMode.WEBSERVER
|
||||
rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json")
|
||||
assert rc.status_code == 404
|
||||
assert rc.json()['detail'] == 'File not found.'
|
||||
|
||||
rc = client_delete(client, f"{BASE_URI}/backtest/history/{file_path.name}")
|
||||
assert rc.status_code == 200
|
||||
|
||||
assert not file_path.exists()
|
||||
assert not meta_path.exists()
|
||||
|
||||
|
||||
def test_health(botclient):
|
||||
ftbot, client = botclient
|
||||
|
||||
|
||||
@@ -799,6 +799,8 @@ async def test_telegram_profit_handle(
|
||||
assert '*Best Performing:* `ETH/USDT: 9.45%`' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Profit factor:*' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Winrate:*' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Expectancy (Ratio):*' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
|
||||
@@ -381,7 +381,7 @@ def test__send_msg(default_conf, mocker, caplog):
|
||||
webhook._send_msg(msg)
|
||||
|
||||
assert post.call_count == 1
|
||||
assert post.call_args[1] == {'data': msg}
|
||||
assert post.call_args[1] == {'data': msg, 'timeout': 10}
|
||||
assert post.call_args[0] == (default_conf['webhook']['url'], )
|
||||
|
||||
post = MagicMock(side_effect=RequestException)
|
||||
@@ -399,7 +399,7 @@ def test__send_msg_with_json_format(default_conf, mocker, caplog):
|
||||
mocker.patch("freqtrade.rpc.webhook.post", post)
|
||||
webhook._send_msg(msg)
|
||||
|
||||
assert post.call_args[1] == {'json': msg}
|
||||
assert post.call_args[1] == {'json': msg, 'timeout': 10}
|
||||
|
||||
|
||||
def test__send_msg_with_raw_format(default_conf, mocker, caplog):
|
||||
@@ -411,7 +411,11 @@ def test__send_msg_with_raw_format(default_conf, mocker, caplog):
|
||||
mocker.patch("freqtrade.rpc.webhook.post", post)
|
||||
webhook._send_msg(msg)
|
||||
|
||||
assert post.call_args[1] == {'data': msg['data'], 'headers': {'Content-Type': 'text/plain'}}
|
||||
assert post.call_args[1] == {
|
||||
'data': msg['data'],
|
||||
'headers': {'Content-Type': 'text/plain'},
|
||||
'timeout': 10
|
||||
}
|
||||
|
||||
|
||||
def test_send_msg_discord(default_conf, mocker):
|
||||
|
||||
@@ -2793,7 +2793,7 @@ def test_manage_open_orders_entry(
|
||||
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
|
||||
# check it does cancel buy orders over the time limit
|
||||
# check it does cancel entry orders over the time limit
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
@@ -2801,7 +2801,7 @@ def test_manage_open_orders_entry(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 0
|
||||
# Custom user buy-timeout is never called
|
||||
# Custom user entry-timeout is never called
|
||||
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||
# Entry adjustment is never called
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
||||
@@ -5023,7 +5023,7 @@ def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker,
|
||||
(8.0, 0.1, 8.0, None),
|
||||
(8.0, 0.1, 7.9, 0.1),
|
||||
])
|
||||
def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
|
||||
def test_apply_fee_conditional(default_conf_usdt, fee, mocker, caplog,
|
||||
amount, fee_abs, wallet, amount_exp):
|
||||
walletmock = mocker.patch('freqtrade.wallets.Wallets.update')
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=wallet)
|
||||
@@ -5048,6 +5048,60 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
|
||||
# Amount is kept as is
|
||||
assert freqtrade.apply_fee_conditional(trade, 'LTC', amount, fee_abs, order) == amount_exp
|
||||
assert walletmock.call_count == 1
|
||||
if fee_abs != 0 and amount_exp is None:
|
||||
assert log_has_re(r"Fee amount.*Eating.*dust\.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
|
||||
(8.0, 0.0, 16, None),
|
||||
(8.0, 0.0, 0, None),
|
||||
(8.0, 0.1, 8, 0.1),
|
||||
(8.0, 0.1, 20, None),
|
||||
(8.0, 0.1, 16.0, None),
|
||||
(8.0, 0.1, 7.9, 0.1),
|
||||
(8.0, 0.1, 12, 0.1),
|
||||
(8.0, 0.1, 15.9, 0.1),
|
||||
])
|
||||
def test_apply_fee_conditional_multibuy(default_conf_usdt, fee, mocker, caplog,
|
||||
amount, fee_abs, wallet, amount_exp):
|
||||
walletmock = mocker.patch('freqtrade.wallets.Wallets.update')
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=wallet)
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
# One closed order
|
||||
order = Order(
|
||||
ft_order_side='buy',
|
||||
order_id='10',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
filled=amount,
|
||||
status="closed"
|
||||
)
|
||||
trade.orders.append(order)
|
||||
# Add additional order - this should NOT eat into dust unless the wallet was bigger already.
|
||||
order1 = Order(
|
||||
ft_order_side='buy',
|
||||
order_id='100',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
)
|
||||
trade.orders.append(order1)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
walletmock.reset_mock()
|
||||
# The new trade amount will be 2x amount - fee / wallet will have to be adapted to this.
|
||||
assert freqtrade.apply_fee_conditional(trade, 'LTC', amount, fee_abs, order1) == amount_exp
|
||||
assert walletmock.call_count == 1
|
||||
if fee_abs != 0 and amount_exp is None:
|
||||
assert log_has_re(r"Fee amount.*Eating.*dust\.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("delta, is_high_delta", [
|
||||
|
||||
@@ -429,6 +429,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert pytest.approx(trade.stop_loss) == 1.99 * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == 1.96 * (1 - 0.1 / leverage)
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
assert pytest.approx(trade.orders[-1].stake_amount) == trade.stake_amount
|
||||
|
||||
# 2nd order - not filling
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120)
|
||||
@@ -473,13 +474,38 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert pytest.approx(trade.orders[1].amount) == 30.150753768 * leverage
|
||||
assert pytest.approx(trade.orders[-1].amount) == 61.538461232 * leverage
|
||||
|
||||
# Full exit
|
||||
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
|
||||
freqtrade.strategy.custom_exit = MagicMock(return_value='Exit now')
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=2.02)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 5
|
||||
assert trade.orders[-1].side == trade.exit_side
|
||||
assert trade.orders[-1].status == 'open'
|
||||
assert trade.orders[-1].price == 2.02
|
||||
assert pytest.approx(trade.amount) == 91.689215 * leverage
|
||||
assert pytest.approx(trade.orders[-1].amount) == 91.689215 * leverage
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
||||
# Process again, should not adjust entry price
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 5
|
||||
assert trade.orders[-1].status == 'open'
|
||||
assert trade.orders[-1].price == 2.02
|
||||
# Adjust entry price cannot be called - this is an exit order
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('leverage', [1, 2])
|
||||
def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, leverage) -> None:
|
||||
default_conf_usdt['position_adjustment_enable'] = True
|
||||
|
||||
spot = leverage == 1
|
||||
if not spot:
|
||||
default_conf_usdt['trading_mode'] = 'futures'
|
||||
default_conf_usdt['margin_mode'] = 'isolated'
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
freqtrade.trading_mode = TradingMode.FUTURES
|
||||
assert freqtrade.trading_mode == TradingMode.FUTURES if not spot else TradingMode.SPOT
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=ticker_usdt,
|
||||
@@ -487,8 +513,11 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
amount_to_precision=lambda s, x, y: y,
|
||||
price_to_precision=lambda s, x, y: y,
|
||||
get_min_pair_stake_amount=MagicMock(return_value=10),
|
||||
get_funding_fees=MagicMock(return_value=0),
|
||||
)
|
||||
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
|
||||
starting_amount = freqtrade.wallets.get_total('USDT')
|
||||
assert starting_amount == 1000
|
||||
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade.strategy.leverage = MagicMock(return_value=leverage)
|
||||
@@ -498,8 +527,14 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.leverage == leverage
|
||||
assert pytest.approx(trade.amount) == 30.0 * leverage
|
||||
assert trade.open_rate == 2.0
|
||||
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == starting_amount - 60
|
||||
if spot:
|
||||
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == starting_amount - 60
|
||||
else:
|
||||
assert freqtrade.wallets.get_total('USDT') == starting_amount
|
||||
|
||||
# Too small size
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-59)
|
||||
@@ -521,6 +556,15 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
assert pytest.approx(trade.amount) == 20.099 * leverage
|
||||
assert trade.open_rate == 2.0
|
||||
assert trade.is_open
|
||||
assert trade.realized_profit > 0.098 * leverage
|
||||
expected_profit = starting_amount - 40.1980 + trade.realized_profit
|
||||
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
|
||||
|
||||
if spot:
|
||||
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
|
||||
else:
|
||||
# total won't change in futures mode, only free / used will.
|
||||
assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit
|
||||
caplog.clear()
|
||||
|
||||
# Sell more than what we got (we got ~20 coins left)
|
||||
@@ -545,3 +589,10 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
assert pytest.approx(trade.stake_amount) == 40.198
|
||||
assert trade.is_open
|
||||
assert log_has_re('Amount to exit is 0.0 due to exchange limits - not exiting.', caplog)
|
||||
expected_profit = starting_amount - 40.1980 + trade.realized_profit
|
||||
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
|
||||
if spot:
|
||||
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
|
||||
else:
|
||||
# total won't change in futures mode, only free / used will.
|
||||
assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit
|
||||
|
||||
+19
-1
@@ -8,7 +8,7 @@ import pandas as pd
|
||||
import pytest
|
||||
|
||||
from freqtrade.misc import (dataframe_to_json, decimals_per_coin, deep_merge_dicts, file_dump_json,
|
||||
file_load_json, json_to_dataframe, pair_to_filename,
|
||||
file_load_json, is_file_in_dir, json_to_dataframe, pair_to_filename,
|
||||
parse_db_uri_for_logging, plural, render_template,
|
||||
render_template_with_fallback, round_coin_value, safe_value_fallback,
|
||||
safe_value_fallback2)
|
||||
@@ -64,6 +64,24 @@ def test_file_load_json(mocker, testdatadir) -> None:
|
||||
assert ret
|
||||
|
||||
|
||||
def test_is_file_in_dir(tmp_path):
|
||||
|
||||
# Create a temporary directory and file
|
||||
dir_path = tmp_path / "subdir"
|
||||
dir_path.mkdir()
|
||||
file_path = dir_path / "test.txt"
|
||||
file_path.touch()
|
||||
|
||||
# Test that the function returns True when the file is in the directory
|
||||
assert is_file_in_dir(file_path, dir_path) is True
|
||||
|
||||
# Test that the function returns False when the file is not in the directory
|
||||
assert is_file_in_dir(file_path, tmp_path) is False
|
||||
|
||||
file_path2 = tmp_path / "../../test2.txt"
|
||||
assert is_file_in_dir(file_path2, tmp_path) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pair,expected_result", [
|
||||
("ETH/BTC", 'ETH_BTC'),
|
||||
("ETH/USDT", 'ETH_USDT'),
|
||||
|
||||
+29
-1
@@ -8,7 +8,8 @@ from sqlalchemy import select
|
||||
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
|
||||
from freqtrade.exceptions import DependencyException
|
||||
from freqtrade.persistence import Trade
|
||||
from tests.conftest import EXMS, create_mock_trades, get_patched_freqtradebot, patch_wallet
|
||||
from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt,
|
||||
get_patched_freqtradebot, patch_wallet)
|
||||
|
||||
|
||||
def test_sync_wallet_at_boot(mocker, default_conf):
|
||||
@@ -341,6 +342,33 @@ def test_sync_wallet_futures_live(mocker, default_conf):
|
||||
assert 'ETH/USDT:USDT' not in freqtrade.wallets._positions
|
||||
|
||||
|
||||
def test_sync_wallet_dry(mocker, default_conf_usdt, fee):
|
||||
default_conf_usdt['dry_run'] = True
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
assert len(freqtrade.wallets._wallets) == 1
|
||||
assert len(freqtrade.wallets._positions) == 0
|
||||
assert freqtrade.wallets.get_total('USDT') == 1000
|
||||
|
||||
create_mock_trades_usdt(fee, is_short=None)
|
||||
|
||||
freqtrade.wallets.update()
|
||||
|
||||
assert len(freqtrade.wallets._wallets) == 5
|
||||
assert len(freqtrade.wallets._positions) == 0
|
||||
bal = freqtrade.wallets.get_all_balances()
|
||||
assert bal['NEO'].total == 10
|
||||
assert bal['XRP'].total == 10
|
||||
assert bal['LTC'].total == 2
|
||||
assert bal['USDT'].total == 922.74
|
||||
|
||||
assert freqtrade.wallets.get_starting_balance() == default_conf_usdt['dry_run_wallet']
|
||||
total = freqtrade.wallets.get_total('LTC')
|
||||
free = freqtrade.wallets.get_free('LTC')
|
||||
used = freqtrade.wallets.get_used('LTC')
|
||||
assert free != 0
|
||||
assert free + used == total
|
||||
|
||||
|
||||
def test_sync_wallet_futures_dry(mocker, default_conf, fee):
|
||||
default_conf['dry_run'] = True
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
|
||||
Reference in New Issue
Block a user