1) extracting edge_conf to a fixture
2) test cased adjusted to Backtesting 3) Formatted backtesting_details a bit
This commit is contained in:
parent
aefc20738a
commit
12e735e831
@ -156,21 +156,6 @@ def default_conf():
|
|||||||
"NEO/BTC"
|
"NEO/BTC"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"edge": {
|
|
||||||
"enabled": False,
|
|
||||||
"process_throttle_secs": 1800,
|
|
||||||
"calculate_since_number_of_days": 14,
|
|
||||||
"total_capital_in_stake_currency": 0.5,
|
|
||||||
"allowed_risk": 0.01,
|
|
||||||
"stoploss_range_min": -0.01,
|
|
||||||
"stoploss_range_max": -0.1,
|
|
||||||
"stoploss_range_step": -0.01,
|
|
||||||
"maximum_winrate": 0.80,
|
|
||||||
"minimum_expectancy": 0.20,
|
|
||||||
"min_trade_number": 15,
|
|
||||||
"max_trade_duration_minute": 1440,
|
|
||||||
"remove_pumps": True
|
|
||||||
},
|
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"token": "token",
|
"token": "token",
|
||||||
@ -794,3 +779,23 @@ def buy_order_fee():
|
|||||||
'status': 'closed',
|
'status': 'closed',
|
||||||
'fee': None
|
'fee': None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def edge_conf(default_conf):
|
||||||
|
default_conf['edge'] = {
|
||||||
|
"enabled": False,
|
||||||
|
"process_throttle_secs": 1800,
|
||||||
|
"calculate_since_number_of_days": 14,
|
||||||
|
"total_capital_in_stake_currency": 0.5,
|
||||||
|
"allowed_risk": 0.01,
|
||||||
|
"stoploss_range_min": -0.01,
|
||||||
|
"stoploss_range_max": -0.1,
|
||||||
|
"stoploss_range_step": -0.01,
|
||||||
|
"maximum_winrate": 0.80,
|
||||||
|
"minimum_expectancy": 0.20,
|
||||||
|
"min_trade_number": 15,
|
||||||
|
"max_trade_duration_minute": 1440,
|
||||||
|
"remove_pumps": False
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_conf
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103, C0330
|
||||||
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
|
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import logging
|
||||||
from freqtrade.tests.conftest import get_patched_freqtradebot
|
from freqtrade.tests.conftest import get_patched_freqtradebot
|
||||||
from freqtrade.edge import Edge
|
from freqtrade.edge import Edge
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
|
from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataframe,
|
||||||
|
_get_frame_time_from_offset)
|
||||||
import arrow
|
import arrow
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
|
||||||
# Cases to be tested:
|
# Cases to be tested:
|
||||||
# 1) Open trade should be removed from the end
|
# 1) Open trade should be removed from the end
|
||||||
# 2) Two complete trades within dataframe (with sell hit for all)
|
# 2) Two complete trades within dataframe (with sell hit for all)
|
||||||
@ -25,6 +28,101 @@ ticker_interval_in_minute = 60
|
|||||||
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
|
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
|
||||||
|
|
||||||
|
|
||||||
|
# Open trade should be removed from the end
|
||||||
|
tc0 = 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, 1]], # enter trade (signal on last candle)
|
||||||
|
stop_loss=-0.99, roi=float('inf'), profit_perc=0.00,
|
||||||
|
trades=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Two complete trades within dataframe(with sell hit for all)
|
||||||
|
tc1 = 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, 1], # enter trade (signal on last candle)
|
||||||
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0], # exit at open
|
||||||
|
[3, 5000, 5025, 4975, 4987, 6172, 1, 0], # no action
|
||||||
|
[4, 5000, 5025, 4975, 4987, 6172, 0, 0], # should enter the trade
|
||||||
|
[5, 5000, 5025, 4975, 4987, 6172, 0, 1], # no action
|
||||||
|
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
|
||||||
|
],
|
||||||
|
stop_loss=-0.99, roi=float('inf'), profit_perc=0.00,
|
||||||
|
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
|
||||||
|
BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
||||||
|
tc2 = BTContainer(data=[
|
||||||
|
# D O H L C V B S
|
||||||
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
|
[1, 5000, 5025, 4600, 4987, 6172, 0, 0], # enter trade, stoploss hit
|
||||||
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||||
|
],
|
||||||
|
stop_loss=-0.01, roi=float('inf'), profit_perc=-0.01,
|
||||||
|
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
|
||||||
|
tc3 = BTContainer(data=[
|
||||||
|
# D O H L C V B S
|
||||||
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
|
[1, 5000, 5025, 4800, 4987, 6172, 0, 0], # enter trade, stoploss hit
|
||||||
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||||
|
],
|
||||||
|
stop_loss=-0.03, roi=float('inf'), profit_perc=-0.03,
|
||||||
|
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||||
|
)
|
||||||
|
|
||||||
|
#5) Stoploss and sell are hit. should sell on stoploss
|
||||||
|
tc4=BTContainer(data = [
|
||||||
|
# D O H L C V B S
|
||||||
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
|
[1, 5000, 5025, 4800, 4987, 6172, 0, 1], # enter trade, stoploss hit, sell signal
|
||||||
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||||
|
],
|
||||||
|
stop_loss = -0.03, roi = float('inf'), profit_perc = -0.03,
|
||||||
|
trades = [BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||||
|
)
|
||||||
|
|
||||||
|
TESTS = [
|
||||||
|
tc0,
|
||||||
|
tc1,
|
||||||
|
tc2,
|
||||||
|
tc3,
|
||||||
|
tc4
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("data", TESTS)
|
||||||
|
def test_edge_results(edge_conf, mocker, caplog, data) -> None:
|
||||||
|
"""
|
||||||
|
run functional tests
|
||||||
|
"""
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
frame = _build_backtest_dataframe(data.data)
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
|
edge.fee = 0
|
||||||
|
|
||||||
|
trades = edge._find_trades_for_stoploss_range(frame, 'TEST/BTC', [data.stop_loss])
|
||||||
|
results = edge._fill_calculable_fields(DataFrame(trades)) if trades else DataFrame()
|
||||||
|
|
||||||
|
print(results)
|
||||||
|
|
||||||
|
assert len(trades) == len(data.trades)
|
||||||
|
|
||||||
|
if not results.empty:
|
||||||
|
assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3)
|
||||||
|
|
||||||
|
for c, trade in enumerate(data.trades):
|
||||||
|
res = results.iloc[c]
|
||||||
|
assert res.exit_type == trade.sell_reason
|
||||||
|
assert res.open_time == _get_frame_time_from_offset(trade.open_tick)
|
||||||
|
assert res.close_time == _get_frame_time_from_offset(trade.close_tick)
|
||||||
|
|
||||||
|
|
||||||
def test_adjust(mocker, default_conf):
|
def test_adjust(mocker, default_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
@ -94,10 +192,10 @@ def _time_on_candle(number):
|
|||||||
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
|
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
|
||||||
|
|
||||||
|
|
||||||
def test_edge_heartbeat_calculate(mocker, default_conf):
|
def test_edge_heartbeat_calculate(mocker, edge_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
heartbeat = default_conf['edge']['process_throttle_secs']
|
heartbeat = edge_conf['edge']['process_throttle_secs']
|
||||||
|
|
||||||
# should not recalculate if heartbeat not reached
|
# should not recalculate if heartbeat not reached
|
||||||
edge._last_updated = arrow.utcnow().timestamp - heartbeat + 1
|
edge._last_updated = arrow.utcnow().timestamp - heartbeat + 1
|
||||||
@ -148,15 +246,15 @@ def test_edge_process_downloaded_data(mocker, default_conf):
|
|||||||
assert edge._last_updated <= arrow.utcnow().timestamp + 2
|
assert edge._last_updated <= arrow.utcnow().timestamp + 2
|
||||||
|
|
||||||
|
|
||||||
def test_process_expectancy(mocker, default_conf):
|
def test_process_expectancy(mocker, edge_conf):
|
||||||
default_conf['edge']['min_trade_number'] = 2
|
edge_conf['edge']['min_trade_number'] = 2
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
|
||||||
def get_fee():
|
def get_fee():
|
||||||
return 0.001
|
return 0.001
|
||||||
|
|
||||||
freqtrade.exchange.get_fee = get_fee
|
freqtrade.exchange.get_fee = get_fee
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
|
||||||
trades = [
|
trades = [
|
||||||
{'pair': 'TEST/BTC',
|
{'pair': 'TEST/BTC',
|
||||||
@ -210,157 +308,3 @@ def test_process_expectancy(mocker, default_conf):
|
|||||||
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
||||||
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
||||||
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
||||||
|
|
||||||
# 1) Open trade should be removed from the end
|
|
||||||
|
|
||||||
|
|
||||||
def test_case_1(mocker, default_conf):
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
|
||||||
|
|
||||||
stoploss = -0.99 # we don't want stoploss to be hit in this test
|
|
||||||
ticker = [
|
|
||||||
# D=Date, B=Buy, O=Open, H=High, L=Low, C=Close, S=Sell
|
|
||||||
# D, B, O, H, L, C, S
|
|
||||||
[3, 1, 12, 25, 11, 20, 0], # ->
|
|
||||||
[4, 0, 20, 30, 19, 25, 1], # -> should enter the trade
|
|
||||||
]
|
|
||||||
|
|
||||||
ticker_df = _build_dataframe(ticker)
|
|
||||||
trades = edge._find_trades_for_stoploss_range(ticker_df, 'TEST/BTC', [stoploss])
|
|
||||||
|
|
||||||
# No trade should be found
|
|
||||||
assert len(trades) == 0
|
|
||||||
|
|
||||||
|
|
||||||
# 2) Two complete trades within dataframe (with sell hit for all)
|
|
||||||
def test_case_2(mocker, default_conf):
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
|
||||||
|
|
||||||
stoploss = -0.99 # we don't want stoploss to be hit in this test
|
|
||||||
ticker = [
|
|
||||||
# D=Date, B=Buy, O=Open, H=High, L=Low, C=Close, S=Sell
|
|
||||||
# D, B, O, H, L, C, S
|
|
||||||
[0, 1, 15, 20, 12, 17, 0], # -> no action
|
|
||||||
[1, 0, 17, 18, 13, 14, 1], # -> should enter the trade as B signal recieved on last candle
|
|
||||||
[2, 0, 14, 15, 11, 12, 0], # -> exit the trade as the sell signal recieved on last candle
|
|
||||||
[3, 1, 12, 25, 11, 20, 0], # -> no action
|
|
||||||
[4, 0, 20, 30, 19, 25, 0], # -> should enter the trade
|
|
||||||
[5, 0, 25, 27, 22, 26, 1], # -> no action
|
|
||||||
[6, 0, 26, 36, 25, 35, 0], # -> should sell
|
|
||||||
]
|
|
||||||
|
|
||||||
ticker_df = _build_dataframe(ticker)
|
|
||||||
trades = edge._find_trades_for_stoploss_range(ticker_df, 'TEST/BTC', [stoploss])
|
|
||||||
|
|
||||||
# Two trades must have occured
|
|
||||||
assert len(trades) == 2
|
|
||||||
|
|
||||||
# First trade check
|
|
||||||
assert trades[0]['open_time'] == _time_on_candle(1)
|
|
||||||
assert trades[0]['close_time'] == _time_on_candle(2)
|
|
||||||
assert trades[0]['open_rate'] == ticker[1][_ohlc['open']]
|
|
||||||
assert trades[0]['close_rate'] == ticker[2][_ohlc['open']]
|
|
||||||
assert trades[0]['exit_type'] == SellType.SELL_SIGNAL
|
|
||||||
##############################################################
|
|
||||||
|
|
||||||
# Second trade check
|
|
||||||
assert trades[1]['open_time'] == _time_on_candle(4)
|
|
||||||
assert trades[1]['close_time'] == _time_on_candle(6)
|
|
||||||
assert trades[1]['open_rate'] == ticker[4][_ohlc['open']]
|
|
||||||
assert trades[1]['close_rate'] == ticker[6][_ohlc['open']]
|
|
||||||
assert trades[1]['exit_type'] == SellType.SELL_SIGNAL
|
|
||||||
##############################################################
|
|
||||||
|
|
||||||
|
|
||||||
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
|
||||||
def test_case_3(mocker, default_conf):
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
|
||||||
|
|
||||||
stoploss = -0.01 # we don't want stoploss to be hit in this test
|
|
||||||
ticker = [
|
|
||||||
# D=Date, B=Buy, O=Open, H=High, L=Low, C=Close, S=Sell
|
|
||||||
# D, B, O, H, L, C, S
|
|
||||||
[0, 1, 15, 20, 12, 17, 0], # -> no action
|
|
||||||
[1, 0, 14, 15, 11, 12, 0], # -> enter to trade, stoploss hit
|
|
||||||
[2, 1, 12, 25, 11, 20, 0], # -> no action
|
|
||||||
]
|
|
||||||
|
|
||||||
ticker_df = _build_dataframe(ticker)
|
|
||||||
trades = edge._find_trades_for_stoploss_range(ticker_df, 'TEST/BTC', [stoploss])
|
|
||||||
|
|
||||||
# Two trades must have occured
|
|
||||||
assert len(trades) == 1
|
|
||||||
|
|
||||||
# First trade check
|
|
||||||
assert trades[0]['open_time'] == _time_on_candle(1)
|
|
||||||
assert trades[0]['close_time'] == _time_on_candle(1)
|
|
||||||
assert trades[0]['open_rate'] == ticker[1][_ohlc['open']]
|
|
||||||
assert trades[0]['close_rate'] == (stoploss + 1) * trades[0]['open_rate']
|
|
||||||
assert trades[0]['exit_type'] == SellType.STOP_LOSS
|
|
||||||
##############################################################
|
|
||||||
|
|
||||||
|
|
||||||
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
|
|
||||||
def test_case_4(mocker, default_conf):
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
|
||||||
|
|
||||||
stoploss = -0.03 # we don't want stoploss to be hit in this test
|
|
||||||
ticker = [
|
|
||||||
# D=Date, B=Buy, O=Open, H=High, L=Low, C=Close, S=Sell
|
|
||||||
# D, B, O, H, L, C, S
|
|
||||||
[0, 1, 15, 20, 12, 17, 0], # -> no action
|
|
||||||
[1, 0, 17, 22, 16.90, 17, 0], # -> enter to trade
|
|
||||||
[2, 0, 16, 17, 14.4, 15.5, 0], # -> stoploss hit
|
|
||||||
[3, 0, 17, 25, 16.9, 22, 0], # -> no action
|
|
||||||
]
|
|
||||||
|
|
||||||
ticker_df = _build_dataframe(ticker)
|
|
||||||
trades = edge._find_trades_for_stoploss_range(ticker_df, 'TEST/BTC', [stoploss])
|
|
||||||
|
|
||||||
# Two trades must have occured
|
|
||||||
assert len(trades) == 1
|
|
||||||
|
|
||||||
# First trade check
|
|
||||||
assert trades[0]['open_time'] == _time_on_candle(1)
|
|
||||||
assert trades[0]['close_time'] == _time_on_candle(2)
|
|
||||||
assert trades[0]['open_rate'] == ticker[1][_ohlc['open']]
|
|
||||||
assert trades[0]['close_rate'] == (stoploss + 1) * trades[0]['open_rate']
|
|
||||||
assert trades[0]['exit_type'] == SellType.STOP_LOSS
|
|
||||||
##############################################################
|
|
||||||
|
|
||||||
|
|
||||||
# 5) Stoploss and sell are hit. should sell on stoploss
|
|
||||||
def test_case_5(mocker, default_conf):
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
|
||||||
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
|
||||||
|
|
||||||
stoploss = -0.03 # we don't want stoploss to be hit in this test
|
|
||||||
ticker = [
|
|
||||||
# D=Date, B=Buy, O=Open, H=High, L=Low, C=Close, S=Sell
|
|
||||||
# D, B, O, H, L, C, S
|
|
||||||
[0, 1, 15, 20, 12, 17, 0], # -> no action
|
|
||||||
[1, 0, 17, 22, 16.90, 17, 0], # -> enter to trade
|
|
||||||
[2, 0, 16, 17, 14.4, 15.5, 1], # -> stoploss hit and also sell signal
|
|
||||||
[3, 0, 17, 25, 16.9, 22, 0], # -> no action
|
|
||||||
]
|
|
||||||
|
|
||||||
ticker_df = _build_dataframe(ticker)
|
|
||||||
trades = edge._find_trades_for_stoploss_range(ticker_df, 'TEST/BTC', [stoploss])
|
|
||||||
|
|
||||||
# Two trades must have occured
|
|
||||||
assert len(trades) == 1
|
|
||||||
|
|
||||||
# First trade check
|
|
||||||
assert trades[0]['open_time'] == _time_on_candle(1)
|
|
||||||
assert trades[0]['close_time'] == _time_on_candle(2)
|
|
||||||
assert trades[0]['open_rate'] == ticker[1][_ohlc['open']]
|
|
||||||
assert trades[0]['close_rate'] == (stoploss + 1) * trades[0]['open_rate']
|
|
||||||
assert trades[0]['exit_type'] == SellType.STOP_LOSS
|
|
||||||
##############################################################
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, C0330, unused-argument
|
||||||
import logging
|
import logging
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user