Merge branch 'develop' into pr/italodamato/6563

This commit is contained in:
Matthias
2022-04-10 09:16:31 +02:00
185 changed files with 34047 additions and 5067 deletions

View File

@@ -3,7 +3,7 @@ from typing import Dict, List, NamedTuple, Optional
import arrow
from pandas import DataFrame
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.exchange import timeframe_to_minutes
@@ -15,10 +15,11 @@ class BTrade(NamedTuple):
"""
Minimalistic Trade result used for functional backtesting
"""
sell_reason: SellType
exit_reason: ExitType
open_tick: int
close_tick: int
buy_tag: Optional[str] = None
enter_tag: Optional[str] = None
is_short: bool = False
class BTContainer(NamedTuple):
@@ -34,10 +35,11 @@ class BTContainer(NamedTuple):
trailing_only_offset_is_reached: bool = False
trailing_stop_positive: Optional[float] = None
trailing_stop_positive_offset: float = 0.0
use_sell_signal: bool = False
use_exit_signal: bool = False
use_custom_stoploss: bool = False
custom_entry_price: Optional[float] = None
custom_exit_price: Optional[float] = None
leverage: float = 1.0
def _get_frame_time_from_offset(offset):
@@ -46,18 +48,18 @@ def _get_frame_time_from_offset(offset):
def _build_backtest_dataframe(data):
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell']
columns = columns + ['buy_tag'] if len(data[0]) == 9 else columns
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'enter_long', 'exit_long',
'enter_short', 'exit_short']
if len(data[0]) == 8:
# No short columns
data = [d + [0, 0] for d in data]
columns = columns + ['enter_tag'] if len(data[0]) == 11 else columns
frame = DataFrame.from_records(data, columns=columns)
frame['date'] = frame['date'].apply(_get_frame_time_from_offset)
# Ensure floats are in place
for column in ['open', 'high', 'low', 'close', 'volume']:
frame[column] = frame[column].astype('float64')
if 'buy_tag' not in columns:
frame['buy_tag'] = None
if 'exit_tag' not in columns:
frame['exit_tag'] = None
# Ensure all candles make kindof sense
assert all(frame['low'] <= frame['close'])

View File

@@ -5,7 +5,7 @@ from pathlib import Path
import pandas as pd
import pytest
from freqtrade.enums import RunMode, SellType
from freqtrade.enums import ExitType, RunMode
from freqtrade.optimize.hyperopt import Hyperopt
from tests.conftest import patch_exchange
@@ -44,7 +44,7 @@ def hyperopt_results():
'profit_abs': [-0.2, 0.4, -0.2, 0.6],
'trade_duration': [10, 30, 10, 10],
'amount': [0.1, 0.1, 0.1, 0.1],
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.STOP_LOSS, SellType.ROI],
'exit_reason': [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI],
'open_date':
[
datetime(2019, 1, 1, 9, 15, 0),

View File

@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.data.history import get_timerange
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.optimize.backtesting import Backtesting
from tests.conftest import patch_exchange
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
@@ -15,21 +15,21 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
# Test 0: Sell with signal sell in candle 3
# Test with Stop-loss at 1%
tc0 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0], # exit with stoploss hit
[3, 5010, 5010, 4980, 5010, 6172, 0, 1],
[4, 5010, 5011, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 1: Stop-Loss Triggered 1% loss
# Test with Stop-loss at 1%
tc1 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4600, 4600, 6172, 0, 0], # exit with stoploss hit
@@ -37,14 +37,14 @@ tc1 = BTContainer(data=[
[4, 4977, 4995, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 2: Minus 4% Low, minus 1% close
# Test with Stop-Loss at 3%
tc2 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4962, 4975, 6172, 0, 0],
@@ -52,7 +52,7 @@ tc2 = BTContainer(data=[
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)]
)
@@ -63,7 +63,7 @@ tc2 = BTContainer(data=[
# Trade-A: Stop-Loss Triggered 2% Loss
# Trade-B: Stop-Loss Triggered 2% Loss
tc3 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4800, 4975, 6172, 0, 0], # exit with stoploss hit
@@ -72,8 +72,8 @@ tc3 = BTContainer(data=[
[5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit
[6, 4950, 4975, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=4, close_tick=5)]
)
# Test 4: Minus 3% / recovery +15%
@@ -81,7 +81,7 @@ tc3 = BTContainer(data=[
# Test with Stop-loss at 2% ROI 6%
# Stop-Loss Triggered 2% Loss
tc4 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5750, 4850, 5750, 6172, 0, 0], # Exit with stoploss hit
@@ -89,13 +89,13 @@ tc4 = BTContainer(data=[
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 5: Drops 0.5% Closes +20%, ROI triggers 3% Gain
# stop-loss: 1%, ROI: 3%
tc5 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4980, 4987, 6172, 1, 0],
[1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5025, 4975, 4987, 6172, 0, 0],
@@ -103,13 +103,13 @@ tc5 = BTContainer(data=[
[4, 4962, 4987, 4962, 4972, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 6: Drops 3% / Recovers 6% Positive / Closes 1% positve, Stop-Loss triggers 2% Loss
# stop-loss: 2% ROI: 5%
tc6 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5300, 4850, 5050, 6172, 0, 0], # Exit with stoploss
@@ -117,13 +117,13 @@ tc6 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 7: 6% Positive / 1% Negative / Close 1% Positve, ROI Triggers 3% Gain
# stop-loss: 2% ROI: 3%
tc7 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5050, 6172, 0, 0],
@@ -131,42 +131,42 @@ tc7 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2)]
)
# Test 8: trailing_stop should raise so candle 3 causes a stoploss.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 2
tc8 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
[2, 5000, 5250, 4750, 4850, 6172, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 9: trailing_stop should raise - high and low in same candle.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 3
tc9 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
[2, 5000, 5050, 4950, 5000, 6172, 0, 0],
[3, 5000, 5200, 4550, 4850, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 10: trailing_stop should raise so candle 3 causes a stoploss
# without applying trailing_stop_positive since stoploss_offset is at 10%.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc10 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -175,14 +175,14 @@ tc10 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=4)]
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=4)]
)
# Test 11: trailing_stop should raise so candle 3 causes a stoploss
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc11 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -191,14 +191,14 @@ tc11 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 12: trailing_stop should raise in candle 2 and cause a stoploss in the same candle
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc12 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
@@ -207,55 +207,55 @@ tc12 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 13: Buy and sell ROI on same candle
# stop-loss: 10% (should not apply), ROI: 1%
tc13 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 4850, 5100, 6172, 0, 0],
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4750, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=1)]
)
# Test 14 - Buy and Stoploss on same candle
# stop-loss: 5%, ROI: 10% (should not apply)
tc14 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4600, 5100, 6172, 0, 0],
[2, 5100, 5251, 4850, 5100, 6172, 0, 0],
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 15 - Buy and ROI on same candle, followed by buy and Stoploss on next candle
# stop-loss: 5%, ROI: 10% (should not apply)
tc15 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4900, 5100, 6172, 1, 0],
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=1),
BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=2, close_tick=2)]
)
# Test 16: Buy, hold for 65 min, then forcesell using roi=-1
# Causes negative profit even though sell-reason is ROI.
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 65 minutes (limits trade duration)
tc16 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5050, 6172, 0, 0],
@@ -263,7 +263,7 @@ tc16 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 17: Buy, hold for 120 mins, then forcesell using roi=-1
@@ -271,7 +271,7 @@ tc16 = BTContainer(data=[
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# Uses open as sell-rate (special case) - since the roi-time is a multiple of the timeframe.
tc17 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5050, 6172, 0, 0],
@@ -279,7 +279,7 @@ tc17 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
@@ -287,7 +287,7 @@ tc17 = BTContainer(data=[
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# uses open_rate as sell-price
tc18 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5200, 6172, 0, 0],
@@ -295,14 +295,14 @@ tc18 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 19: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3.
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# uses calculated ROI (1%) as sell rate, otherwise identical to tc18
tc19 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5200, 6172, 0, 0],
@@ -310,14 +310,14 @@ tc19 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4550, 4975, 4550, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 20: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3.
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# uses calculated ROI (1%) as sell rate, otherwise identical to tc18
tc20 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5200, 6172, 0, 0],
@@ -325,7 +325,7 @@ tc20 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4925, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 21: trailing_stop ROI collision.
@@ -333,7 +333,7 @@ tc20 = BTContainer(data=[
# which cannot happen in reality
# stop-loss: 10%, ROI: 4%, Trailing stop adjusted at the sell candle
tc21 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
@@ -342,14 +342,14 @@ tc21 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2)]
)
# Test 22: trailing_stop Raises in candle 2 - but ROI applies at the same time.
# applying a positive trailing stop of 3% - ROI should apply before trailing stop.
# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2
tc22 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -358,17 +358,34 @@ tc22 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2)]
)
# Test 23: trailing_stop Raises in candle 2 (does not trigger)
# Test 23: trailing_stop Raises in candle 2 - but ROI applies at the same time.
# applying a positive trailing stop of 3% - ROI should apply before trailing stop.
# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2
tc23 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
[1, 5000, 5050, 4900, 4900, 6172, 0, 0, 0, 0],
[2, 4900, 4900, 4749, 4900, 6172, 0, 0, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]],
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2, is_short=True)]
)
# Test 24: trailing_stop Raises in candle 2 (does not trigger)
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
# ROI is changed after this to 4%, dropping ROI below trailing_stop_positive, causing a sell
# in the candle after the raised stoploss candle with ROI reason.
# Stoploss would trigger in this candle too, but it's no longer relevant.
# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2, ROI adjusted in candle 3 (causing the sell)
tc23 = BTContainer(data=[
# D O H L C V B S
tc24 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -377,74 +394,107 @@ tc23 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 24: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Stoploss wins over Sell-signal (because sell-signal is acted on in the next candle)
tc24 = BTContainer(data=[
# D O H L C V B S
tc25 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
[3, 5010, 5010, 4855, 5010, 6172, 0, 1], # Triggers stoploss + sellsignal
[4, 5010, 5010, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_exit_signal=True,
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Test 26: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Sell-signal wins over stoploss
tc25 = BTContainer(data=[
# D O H L C V B S
tc26 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
[3, 5010, 5010, 4986, 5010, 6172, 0, 1],
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 26: Sell with signal sell in candle 3 (ROI at signal candle)
# Test 27: (copy of test26 with leverage)
# Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Sell-signal wins over stoploss
tc27 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
[3, 5010, 5010, 4986, 5010, 6172, 0, 1],
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
leverage=5.0,
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 28: (copy of test26 with leverage and as short)
# Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Sell-signal wins over stoploss
tc28 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 0, 0, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0, 0, 0],
[3, 5010, 5010, 4986, 5010, 6172, 0, 0, 0, 1],
[4, 4990, 5010, 4855, 4995, 6172, 0, 0, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0, 0, 0]],
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
leverage=5.0,
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4, is_short=True)]
)
# Test 29: Sell with signal sell in candle 3 (ROI at signal candle)
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger)
# Sell-signal wins over stoploss
tc26 = BTContainer(data=[
# D O H L C V B S
tc29 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
[3, 5010, 5251, 4986, 5010, 6172, 0, 1], # Triggers ROI, sell-signal
[4, 5010, 5010, 4855, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_exit_signal=True,
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 27: Sell with signal sell in candle 3 (ROI at signal candle)
# Test 30: Sell with signal sell in candle 3 (ROI at signal candle)
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger) - Wins over Sell-signal
tc27 = BTContainer(data=[
# D O H L C V B S
tc30 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
[3, 5010, 5012, 4986, 5010, 6172, 0, 1], # sell-signal
[4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 28: trailing_stop should raise so candle 3 causes a stoploss
# Test 31: trailing_stop should raise so candle 3 causes a stoploss
# Same case than tc11 - but candle 3 "gaps down" - the stoploss will be above the candle,
# therefore "open" will be used
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc28 = BTContainer(data=[
# D O H L C V B S
tc31 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -453,14 +503,33 @@ tc28 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 29: trailing_stop should be triggered by low of next candle, without adjusting stoploss using
# Test 32: (Short of test 31) trailing_stop should raise so candle 3 causes a stoploss
# Same case than tc11 - but candle 3 "gaps down" - the stoploss will be above the candle,
# therefore "open" will be used
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc32 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
[1, 5000, 5050, 4890, 4890, 6172, 0, 0, 0, 0],
[2, 4890, 4890, 4749, 4890, 6172, 0, 0, 0, 0],
[3, 5150, 5350, 4950, 4950, 6172, 0, 0, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[
BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3, is_short=True)
]
)
# Test 33: trailing_stop should be triggered by low of next candle, without adjusting stoploss using
# high of stoploss candle.
# stop-loss: 10%, ROI: 10% (should not apply)
tc29 = BTContainer(data=[
# D O H L C V B S
tc33 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 5000, 5000, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Triggers trailing-stoploss
@@ -468,13 +537,13 @@ tc29 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 30: trailing_stop should be triggered immediately on trade open candle.
# Test 34: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 10%, ROI: 10% (should not apply)
tc30 = BTContainer(data=[
# D O H L C V B S
tc34 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4900, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -482,13 +551,13 @@ tc30 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_stop_positive=0.01,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 31: trailing_stop should be triggered immediately on trade open candle.
# Test 35: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 10%, ROI: 10% (should not apply)
tc31 = BTContainer(data=[
# D O H L C V B S
tc35 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4900, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -497,47 +566,68 @@ tc31 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 32: trailing_stop should be triggered immediately on trade open candle.
# Test 36: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 1%, ROI: 10% (should not apply)
tc32 = BTContainer(data=[
# D O H L C V B S
tc36 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # enter trade (signal on last candle) and stop
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # enter trade and stop
[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, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 33: trailing_stop should be triggered immediately on trade open candle.
# Test 37: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 1%, ROI: 10% (should not apply)
tc33 = BTContainer(data=[
# D O H L C V B S BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0, None], # enter trade (signal on last candle) and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, None],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, None],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, None]],
tc37 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 0, 0, 'buy_signal_01'],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0, None], # enter trade and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0, None],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0, None],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0, None]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(
sell_reason=SellType.TRAILING_STOP_LOSS,
exit_reason=ExitType.TRAILING_STOP_LOSS,
open_tick=1,
close_tick=1,
buy_tag='buy_signal_01'
enter_tag='buy_signal_01'
)]
)
# Test 38: trailing_stop should be triggered immediately on trade open candle.
# copy of Test37 using shorts.
# stop-loss: 1%, ROI: 10% (should not apply)
tc38 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0, 'short_signal_01'],
[1, 5000, 5049, 4500, 5000, 6172, 0, 0, 0, 0, None], # enter trade and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0, None],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0, None],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0, None]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(
exit_reason=ExitType.TRAILING_STOP_LOSS,
open_tick=1,
close_tick=1,
enter_tag='short_signal_01',
is_short=True,
)]
)
# 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
# Test 39: Custom-entry-price below all candles should timeout - so no trade happens.
tc39 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[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],
@@ -547,9 +637,9 @@ tc34 = BTContainer(data=[
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
# Test 40: Custom-entry-price above all candles should have rate adjusted to "entry candle high"
tc40 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[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],
@@ -557,16 +647,30 @@ tc35 = BTContainer(data=[
[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)
BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)
])
# Test 41: Custom-entry-price above all candles should have rate adjusted to "entry candle high"
tc41 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0], # Timeout
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01,
custom_entry_price=4000,
trades=[
BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1, is_short=True)
]
)
# Test 36: Custom-entry-price around candle low
# Test 42: Custom-entry-price around candle low
# Would cause immediate ROI exit, but since the trade was entered
# below open, we treat this as cheating, and delay the sell by 1 candle.
# details: https://github.com/freqtrade/freqtrade/issues/6261
tc36 = BTContainer(data=[
# D O H L C V B S BT
tc42 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 4999, 6172, 0, 0], # Enter and immediate ROI
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -574,14 +678,14 @@ tc36 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
custom_entry_price=4952,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2)]
)
# Test 37: Custom-entry-price around candle low
# Test 43: Custom-entry-price around candle low
# Would cause immediate ROI exit below close
# details: https://github.com/freqtrade/freqtrade/issues/6261
tc37 = BTContainer(data=[
# D O H L C V B S BT
tc43 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5400, 5500, 4951, 5100, 6172, 0, 0], # Enter and immediate ROI
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -589,37 +693,65 @@ tc37 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
custom_entry_price=4952,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=1)]
)
# Test 38: Custom exit price below all candles
# Test 44: Custom exit price below all candles
# Price adjusted to candle Low.
tc38 = BTContainer(data=[
# D O H L C V B S BT
tc44 = BTContainer(data=[
# D O H L C V EL XL ES Xs 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,
use_exit_signal=True,
custom_exit_price=4552,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=3)]
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=3)]
)
# Test 39: Custom exit price above all candles
# Test 45: Custom exit price above all candles
# causes sell signal timeout
tc39 = BTContainer(data=[
# D O H L C V B S BT
tc45 = BTContainer(data=[
# D O H L C V EL XL ES Xs 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
[2, 4950, 5250, 4900, 5100, 6172, 0, 1], # exit - entry 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,
use_exit_signal=True,
custom_exit_price=6052,
trades=[BTrade(sell_reason=SellType.FORCE_SELL, open_tick=1, close_tick=4)]
trades=[BTrade(exit_reason=ExitType.FORCE_EXIT, open_tick=1, close_tick=4)]
)
# Test 46: (Short of tc45) Custom short exit price above below candles
# causes sell signal timeout
tc46 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
[1, 5000, 5000, 4951, 5000, 6172, 0, 0, 0, 0],
[2, 4910, 5150, 4910, 5100, 6172, 0, 0, 0, 1], # exit - entry timeout
[3, 5100, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
[4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
use_exit_signal=True,
custom_exit_price=4700,
trades=[BTrade(exit_reason=ExitType.FORCE_EXIT, open_tick=1, close_tick=4, is_short=True)]
)
# Test 47: Colliding long and short signal
tc47 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0],
[2, 4900, 5250, 4900, 5100, 6172, 0, 0, 0, 0],
[3, 5100, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
[4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
use_exit_signal=True,
trades=[]
)
@@ -664,11 +796,19 @@ TESTS = [
tc37,
tc38,
tc39,
tc40,
tc41,
tc42,
tc43,
tc44,
tc45,
tc46,
tc47,
]
@pytest.mark.parametrize("data", TESTS)
def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer) -> None:
"""
run functional tests
"""
@@ -681,22 +821,27 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
if data.trailing_stop_positive is not None:
default_conf["trailing_stop_positive"] = data.trailing_stop_positive
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
default_conf["use_sell_signal"] = data.use_sell_signal
default_conf["use_exit_signal"] = data.use_exit_signal
mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0)
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'))
mocker.patch("freqtrade.exchange.Binance.get_max_leverage", return_value=100)
patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data)
backtesting = Backtesting(default_conf)
# TODO: Should we initialize this properly??
backtesting._can_short = True
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.required_startup = 0
backtesting.strategy.advise_buy = lambda a, m: frame
backtesting.strategy.advise_sell = lambda a, m: frame
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)
pair = "UNITTEST/BTC"
@@ -715,8 +860,9 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.sell_reason == trade.sell_reason.value
assert res.buy_tag == trade.buy_tag
res: BTrade = results.iloc[c]
assert res.exit_reason == trade.exit_reason.value
assert res.enter_tag == trade.enter_tag
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)
assert res.is_short == trade.is_short

View File

@@ -19,27 +19,27 @@ from freqtrade.data.btanalysis import BT_DATA_COLUMNS, evaluate_result_multi
from freqtrade.data.converter import clean_ohlcv_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange
from freqtrade.enums import RunMode, SellType
from freqtrade.enums import ExitType, RunMode
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
from freqtrade.resolvers import StrategyResolver
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
ORDER_TYPES = [
{
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
},
{
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}]
@@ -134,12 +134,14 @@ def _trend(signals, buy_value, sell_value):
n = len(signals['low'])
buy = np.zeros(n)
sell = np.zeros(n)
for i in range(0, len(signals['buy'])):
for i in range(0, len(signals['date'])):
if random.random() > 0.5: # Both buy and sell signals at same timeframe
buy[i] = buy_value
sell[i] = sell_value
signals['buy'] = buy
signals['sell'] = sell
signals['enter_long'] = buy
signals['exit_long'] = sell
signals['enter_short'] = 0
signals['exit_short'] = 0
return signals
@@ -154,8 +156,10 @@ def _trend_alternate(dataframe=None, metadata=None):
buy[i] = 1
else:
sell[i] = 1
signals['buy'] = buy
signals['sell'] = sell
signals['enter_long'] = buy
signals['exit_long'] = sell
signals['enter_short'] = 0
signals['exit_short'] = 0
return dataframe
@@ -166,7 +170,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--export', 'none'
]
@@ -201,7 +205,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--timeframe', '1m',
'--enable-position-stacking',
@@ -251,7 +255,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--starting-balance', '2'
]
@@ -262,7 +266,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--starting-balance', '0.5'
]
@@ -280,7 +284,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
pargs = get_args(args)
start_backtesting(pargs)
@@ -302,8 +306,8 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
assert backtesting.config == default_conf
assert backtesting.timeframe == '5m'
assert callable(backtesting.strategy.advise_all_indicators)
assert callable(backtesting.strategy.advise_buy)
assert callable(backtesting.strategy.advise_sell)
assert callable(backtesting.strategy.advise_entry)
assert callable(backtesting.strategy.advise_exit)
assert isinstance(backtesting.strategy.dp, DataProvider)
get_fee.assert_called()
assert backtesting.fee == 0.5
@@ -313,7 +317,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
patch_exchange(mocker)
del default_conf['timeframe']
default_conf['strategy_list'] = ['StrategyTestV2',
default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY,
'HyperoptableStrategy']
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
@@ -350,7 +354,6 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
assert len(processed['UNITTEST/BTC']) == 102
# Load strategy to compare the result between Backtesting function and strategy are the same
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
processed2 = strategy.advise_all_indicators(data)
@@ -494,16 +497,17 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
Backtesting(default_conf)
# Multiple strategies
default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1']
default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'TestStrategyLegacyV1']
with pytest.raises(OperationalException,
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
Backtesting(default_conf)
def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
default_conf['use_sell_signal'] = False
default_conf['use_exit_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=float('inf'))
patch_exchange(mocker)
default_conf['stake_amount'] = 'unlimited'
default_conf['max_open_trades'] = 2
@@ -520,7 +524,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
0.0012, # High
'', # Buy Signal Name
]
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert isinstance(trade, LocalTrade)
assert trade.stake_amount == 495
@@ -528,42 +532,123 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
LocalTrade.trades_open.append(trade)
LocalTrade.trades_open.append(trade)
backtesting.wallets.update()
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is None
LocalTrade.trades_open.pop()
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is not None
backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
backtesting.wallets.update()
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade
assert trade.stake_amount == 123.5
# In case of error - use proposed stake
backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade
assert trade.stake_amount == 495
assert trade.is_short is False
trade = backtesting._enter_trade(pair, row=row, direction='short')
assert trade
assert trade.stake_amount == 495
assert trade.is_short is True
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=300.0)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade
assert trade.stake_amount == 300.0
def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
default_conf_usdt['use_exit_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=float('inf'))
mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=100)
patch_exchange(mocker)
default_conf_usdt['stake_amount'] = 300
default_conf_usdt['max_open_trades'] = 2
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt['stake_currency'] = 'USDT'
default_conf_usdt['exchange']['pair_whitelist'] = ['.*']
backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/USDT:USDT'
row = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
0.001, # Open
0.0012, # High
0.00099, # Low
0.0011, # Close
1, # enter_long
0, # exit_long
1, # enter_short
0, # exit_hsort
'', # Long Signal Name
'', # Short Signal Name
'', # Exit Signal Name
]
backtesting.strategy.leverage = MagicMock(return_value=5.0)
mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt",
return_value=(0.01, 0.01))
# leverage = 5
# ep1(trade.open_rate) = 0.001
# position(trade.amount) = 1500000
# stake_amount = 300 -> wb = 300 / 5 = 60
# mmr = 0.01
# cum_b = 0.01
# side_1: -1 if is_short else 1
# liq_buffer = 0.05
#
# Binance, Long
# liquidation_price
# = ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
# = ((300 + 0.01) - (1 * 1500000 * 0.001)) / ((1500000 * 0.01) - (1 * 1500000))
# = 0.0008080740740740741
# freqtrade_liquidation_price = liq + (abs(open_rate - liq) * liq_buffer * side_1)
# = 0.0008080740740740741 + ((0.001 - 0.0008080740740740741) * 0.05 * 1)
# = 0.0008176703703703704
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert pytest.approx(trade.liquidation_price) == 0.00081767037
# Binance, Short
# liquidation_price
# = ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
# = ((300 + 0.01) - ((-1) * 1500000 * 0.001)) / ((1500000 * 0.01) - ((-1) * 1500000))
# = 0.0011881254125412541
# freqtrade_liquidation_price = liq + (abs(open_rate - liq) * liq_buffer * side_1)
# = 0.0011881254125412541 + (abs(0.001 - 0.0011881254125412541) * 0.05 * -1)
# = 0.0011787191419141915
trade = backtesting._enter_trade(pair, row=row, direction='short')
assert pytest.approx(trade.liquidation_price) == 0.0011787191
# Stake-amount too high!
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0)
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is None
# Stake-amount throwing error
mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount",
side_effect=DependencyException)
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is None
def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
default_conf['use_sell_signal'] = False
default_conf['use_exit_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=float('inf'))
patch_exchange(mocker)
default_conf['timeframe_detail'] = '1m'
default_conf['max_open_trades'] = 2
@@ -572,63 +657,72 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
pair = 'UNITTEST/BTC'
row = [
pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=55, tzinfo=timezone.utc),
1, # Buy
200, # Open
201, # Close
0, # Sell
195, # Low
201.5, # High
'', # Buy Signal Name
195, # Low
201, # Close
1, # enter_long
0, # exit_long
0, # enter_short
0, # exit_hsort
'', # Long Signal Name
'', # Short Signal Name
'', # Exit Signal Name
]
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert isinstance(trade, LocalTrade)
row_sell = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
0, # Buy
200, # Open
201, # Close
0, # Sell
195, # Low
210.5, # High
'', # Buy Signal Name
195, # Low
201, # Close
0, # enter_long
0, # exit_long
0, # enter_short
0, # exit_short
'', # long Signal Name
'', # Short Signal Name
'', # Exit Signal Name
]
row_detail = pd.DataFrame(
[
[
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
1, 200, 199, 0, 197, 200.1, '', '',
200, 200.1, 197, 199, 1, 0, 0, 0, '', '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=1, tzinfo=timezone.utc),
0, 199, 199.5, 0, 199, 199.7, '', '',
199, 199.7, 199, 199.5, 0, 0, 0, 0, '', '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=2, tzinfo=timezone.utc),
0, 199.5, 200.5, 0, 199, 200.8, '', '',
199.5, 200.8, 199, 200.9, 0, 0, 0, 0, '', '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=3, tzinfo=timezone.utc),
0, 200.5, 210.5, 0, 193, 210.5, '', '', # ROI sell (?)
200.5, 210.5, 193, 210.5, 0, 0, 0, 0, '', '', '', # ROI sell (?)
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=4, tzinfo=timezone.utc),
0, 200, 199, 0, 193, 200.1, '', '',
200, 200.1, 193, 199, 0, 0, 0, 0, '', '', '',
],
], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"]
], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag']
)
# No data available.
res = backtesting._get_sell_trade_entry(trade, row_sell)
assert res is not None
assert res.sell_reason == SellType.ROI.value
assert res.exit_reason == ExitType.ROI.value
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
# Enter new trade
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert isinstance(trade, LocalTrade)
# Assign empty ... no result.
backtesting.detail_data[pair] = pd.DataFrame(
[], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"])
[], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag'])
res = backtesting._get_sell_trade_entry(trade, row)
assert res is None
@@ -638,7 +732,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
res = backtesting._get_sell_trade_entry(trade, row_sell)
assert res is not None
assert res.sell_reason == SellType.ROI.value
assert res.exit_reason == ExitType.ROI.value
# Sell at minute 3 (not available above!)
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
sell_order = res.select_order('sell', True)
@@ -646,9 +740,10 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_sell_signal'] = False
default_conf['use_exit_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=float('inf'))
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
@@ -686,7 +781,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'trade_duration': [235, 40],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'sell_reason': [SellType.ROI.value, SellType.ROI.value],
'exit_reason': [ExitType.ROI.value, ExitType.ROI.value],
'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236],
@@ -694,7 +789,8 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10501, 0.1038888],
'is_open': [False, False],
'buy_tag': [None, None]
'enter_tag': [None, None],
"is_short": [False, False],
})
pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair]
@@ -711,9 +807,10 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_sell_signal'] = False
default_conf['use_exit_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=float('inf'))
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
@@ -735,6 +832,36 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
assert len(results['results']) == 1
def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_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=float('inf'))
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
timerange = TimeRange('date', None, 1517227800, 0)
backtesting.required_startup = 100
backtesting.timerange = timerange
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
df = data['UNITTEST/BTC']
df.loc[:, 'date'] = df.loc[:, 'date'] - timedelta(days=1)
# Trimming 100 candles, so after 2nd trimming, no candle is left.
df = df.iloc[:100]
data['XRP/USDT'] = df
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
def test_processed(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
@@ -751,9 +878,10 @@ def test_processed(default_conf, mocker, testdatadir) -> None:
def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_sell_signal'] = False
default_conf['use_exit_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])
@@ -770,7 +898,7 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
dp = backtesting.strategy.dp
df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe)
current_candle = df.iloc[-1].squeeze()
assert current_candle['buy'] == 1
assert current_candle['enter_long'] == 1
candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle['date'])
assert candle_date == current_time
@@ -802,6 +930,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
default_conf['enable_protections'] = True
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'))
tests = [
['sine', 9],
['raise', 10],
@@ -809,7 +938,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
['sine', 9],
['raise', 10],
]
# While buy-signals are unrealistic, running backtesting
# While entry-signals are unrealistic, running backtesting
# over and over again should not cause different results
for [contour, numres] in tests:
# Debug output for random test failure
@@ -836,14 +965,15 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
default_conf['enable_protections'] = True
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'))
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
# While buy-signals are unrealistic, running backtesting
# While entry-signals are unrealistic, running backtesting
# over and over again should not cause different results
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == expected
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
# Override the default buy trend function in our StrategyTestV2
# Override the default buy trend function in our StrategyTest
def fun(dataframe=None, pair=None):
buy_value = 1
sell_value = 1
@@ -852,14 +982,14 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf)
assert result['results'].empty
def test_backtest_only_sell(mocker, default_conf, testdatadir):
# Override the default buy trend function in our StrategyTestV2
# Override the default buy trend function in our StrategyTest
def fun(dataframe=None, pair=None):
buy_value = 0
sell_value = 1
@@ -868,14 +998,15 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf)
assert result['results'].empty
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
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'))
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
pair='UNITTEST/BTC', datadir=testdatadir)
@@ -883,8 +1014,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
backtesting = Backtesting(default_conf)
backtesting.required_startup = 0
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate # Override
backtesting.strategy.advise_sell = _trend_alternate # Override
backtesting.strategy.advise_entry = _trend_alternate # Override
backtesting.strategy.advise_exit = _trend_alternate # Override
result = backtesting.backtest(**backtest_conf)
# 200 candles in backtest data
# won't buy on first (shifted by 1)
@@ -915,11 +1046,14 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
multi = 20
else:
multi = 18
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
dataframe['enter_long'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['exit_long'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
dataframe['enter_short'] = 0
dataframe['exit_short'] = 0
return dataframe
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'))
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker)
@@ -935,8 +1069,8 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate_hold # Override
backtesting.strategy.advise_sell = _trend_alternate_hold # Override
backtesting.strategy.advise_entry = _trend_alternate_hold # Override
backtesting.strategy.advise_exit = _trend_alternate_hold # Override
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
@@ -959,8 +1093,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
offset = 1 if tres == 0 else 0
removed_candles = len(data[pair]) - offset - backtesting.strategy.startup_candle_count
assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles
assert len(backtesting.dataprovider.get_analyzed_dataframe(
'NXT/BTC', '5m')[0]) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count
assert len(
backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0]
) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count
backtest_conf = {
'processed': deepcopy(processed),
@@ -986,7 +1121,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', str(testdatadir),
'--timeframe', '1m',
'--timerange', '1510694220-1510700340',
@@ -1016,10 +1151,10 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
default_conf.update({
"use_sell_signal": True,
"sell_profit_only": False,
"sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": False,
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
})
patch_exchange(mocker)
backtestmock = MagicMock(return_value={
@@ -1043,7 +1178,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
text_table_bt_results=text_table_mock,
text_table_strategy=strattable_mock,
generate_pair_metrics=MagicMock(),
generate_sell_reason_stats=sell_reason_mock,
generate_exit_reason_stats=sell_reason_mock,
generate_strategy_comparison=strat_summary,
generate_daily_stats=MagicMock(),
)
@@ -1059,7 +1194,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'--enable-position-stacking',
'--disable-max-market-positions',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1',
]
args = get_args(args)
@@ -1082,7 +1217,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy StrategyTestV2',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy TestStrategyLegacyV1',
]
@@ -1093,10 +1228,10 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
default_conf.update({
"use_sell_signal": True,
"sell_profit_only": False,
"sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": False,
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
})
patch_exchange(mocker)
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
@@ -1112,7 +1247,9 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI]
"is_short": [False, False],
'exit_reason': [ExitType.ROI, ExitType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1],
@@ -1129,7 +1266,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
"is_short": [False, False, False],
'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{
@@ -1168,7 +1306,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'--disable-max-market-positions',
'--breakdown', 'day',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1',
]
args = get_args(args)
@@ -1185,7 +1323,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy StrategyTestV2',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy TestStrategyLegacyV1',
]
@@ -1194,22 +1332,128 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'SELL REASON STATS' in captured.out
assert 'EXIT REASON STATS' in captured.out
assert 'DAY BREAKDOWN' in captured.out
assert 'LEFT OPEN TRADES REPORT' in captured.out
assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out
assert 'STRATEGY SUMMARY' in captured.out
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
caplog, testdatadir, capsys):
# Tests detail-data loading
default_conf_usdt.update({
"trading_mode": "futures",
"margin_mode": "isolated",
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
"strategy": CURRENT_TEST_STRATEGY,
})
patch_exchange(mocker)
result1 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT'],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_date': pd.to_datetime(['2021-11-18 18:00:00',
'2021-11-18 03:00:00', ], utc=True
),
'close_date': pd.to_datetime(['2021-11-18 20:00:00',
'2021-11-18 05:00:00', ], utc=True),
'trade_duration': [235, 40],
'is_open': [False, False],
'is_short': [False, False],
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'exit_reason': [ExitType.ROI, ExitType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT', 'XRP/USDT'],
'profit_ratio': [0.03, 0.01, 0.1],
'profit_abs': [0.01, 0.02, 0.2],
'open_date': pd.to_datetime(['2021-11-19 18:00:00',
'2021-11-19 03:00:00',
'2021-11-19 05:00:00'], utc=True
),
'close_date': pd.to_datetime(['2021-11-19 20:00:00',
'2021-11-19 05:00:00',
'2021-11-19 08:00:00'], utc=True),
'trade_duration': [47, 40, 20],
'is_open': [False, False, False],
'is_short': [False, False, False],
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{
'results': result1,
'config': default_conf_usdt,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
},
{
'results': result2,
'config': default_conf_usdt,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
}
])
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['XRP/USDT']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
patched_configuration_load_config_file(mocker, default_conf_usdt)
args = [
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--timeframe', '1h',
]
args = get_args(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--timeframe detected ... Using timeframe: 1h ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2021-11-17 01:00:00 '
'up to 2021-11-21 03:00:00 (4 days).',
'Backtesting with data from 2021-11-17 21:00:00 '
'up to 2021-11-21 03:00:00 (3 days).',
'XRP/USDT, funding_rate, 8h, data starts at 2021-11-18 00:00:00',
'XRP/USDT, mark, 8h, data starts at 2021-11-18 00:00:00',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
]
for line in exists:
assert log_has(line, caplog)
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'EXIT REASON STATS' in captured.out
assert 'LEFT OPEN TRADES REPORT' in captured.out
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
caplog, testdatadir, capsys):
# Tests detail-data loading
default_conf.update({
"use_sell_signal": True,
"sell_profit_only": False,
"sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": False,
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
})
patch_exchange(mocker)
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
@@ -1222,10 +1466,11 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'2018-01-30 05:35:00', ], utc=True),
'trade_duration': [235, 40],
'is_open': [False, False],
'is_short': [False, False],
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI]
'exit_reason': [ExitType.ROI, ExitType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1],
@@ -1239,10 +1484,11 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'2018-01-30 08:30:00'], utc=True),
'trade_duration': [47, 40, 20],
'is_open': [False, False, False],
'is_short': [False, False, False],
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{
@@ -1278,7 +1524,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'--timeframe', '5m',
'--timeframe-detail', '1m',
'--strategy-list',
'StrategyTestV2'
CURRENT_TEST_STRATEGY
]
args = get_args(args)
start_backtesting(args)
@@ -1292,7 +1538,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'up to 2019-10-13 11:10:00 (2 days).',
'Backtesting with data from 2019-10-11 01:40:00 '
'up to 2019-10-13 11:10:00 (2 days).',
'Running backtesting for Strategy StrategyTestV2',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
]
for line in exists:
@@ -1300,7 +1546,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'SELL REASON STATS' in captured.out
assert 'EXIT REASON STATS' in captured.out
assert 'LEFT OPEN TRADES REPORT' in captured.out
@@ -1311,10 +1557,10 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testdatadir, run_id,
start_delta, cache):
default_conf.update({
"use_sell_signal": True,
"sell_profit_only": False,
"sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": False,
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
})
patch_exchange(mocker)
backtestmock = MagicMock(return_value={

View File

@@ -8,15 +8,16 @@ from arrow import Arrow
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.history import get_timerange
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.optimize.backtesting import Backtesting
from tests.conftest import patch_exchange
def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_sell_signal'] = False
default_conf['use_exit_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=float('inf'))
patch_exchange(mocker)
default_conf.update({
"stake_amount": 100.0,
@@ -59,7 +60,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
'trade_duration': [200, 40],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'sell_reason': [SellType.ROI.value, SellType.ROI.value],
'exit_reason': [ExitType.ROI.value, ExitType.ROI.value],
'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236],
@@ -67,7 +68,8 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10481985, 0.1038888],
'is_open': [False, False],
'buy_tag': [None, None],
'enter_tag': [None, None],
'is_short': [False, False],
})
pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair]

View File

@@ -6,7 +6,8 @@ from unittest.mock import MagicMock
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge
from freqtrade.enums import RunMode
from freqtrade.optimize.edge_cli import EdgeCli
from tests.conftest import get_args, log_has, patch_exchange, patched_configuration_load_config_file
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, patch_exchange,
patched_configuration_load_config_file)
def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@@ -15,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
args = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
@@ -44,7 +45,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
args = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--timeframe', '1m',
'--timerange', ':100',
@@ -78,7 +79,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
args = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
pargs = get_args(args)
start_edge(pargs)

View File

@@ -10,7 +10,7 @@ from filelock import Timeout
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
from freqtrade.data.history import load_data
from freqtrade.enums import RunMode, SellType
from freqtrade.enums import ExitType, RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
@@ -18,32 +18,32 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.optimize.space import SKDecimal
from freqtrade.strategy.hyper import IntParameter
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
def generate_result_metrics():
return {
'trade_count': 1,
'total_trades': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 0.01,
'duration': 20.0,
'wins': 1,
'draws': 0,
'losses': 0,
'profit_mean': 0.01,
'profit_total_abs': 0.001,
'profit_total': 0.01,
'holding_avg': timedelta(minutes=20),
'max_drawdown': 0.001,
'max_drawdown_abs': 0.001,
'loss': 0.001,
'is_initial_point': 0.001,
'is_random': False,
'is_best': 1,
}
'trade_count': 1,
'total_trades': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 0.01,
'duration': 20.0,
'wins': 1,
'draws': 0,
'losses': 0,
'profit_mean': 0.01,
'profit_total_abs': 0.001,
'profit_total': 0.01,
'holding_avg': timedelta(minutes=20),
'max_drawdown': 0.001,
'max_drawdown_abs': 0.001,
'loss': 0.001,
'is_initial_point': 0.001,
'is_random': False,
'is_best': 1,
}
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@@ -145,7 +145,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
args = [
'hyperopt',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--starting-balance', '0.5'
]
@@ -331,8 +331,8 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
# Should be called for historical candle data
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
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")
@@ -357,9 +357,10 @@ def test_hyperopt_format_results(hyperopt):
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_EXIT]
}),
'config': hyperopt.config,
'locks': [],
@@ -427,9 +428,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_EXIT]
}),
'config': hyperopt_conf,
'locks': [],
@@ -688,8 +690,8 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
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")
@@ -761,8 +763,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
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")
@@ -803,8 +805,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
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")
@@ -851,6 +853,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
'spaces': ['all']
})
hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)

View File

@@ -10,7 +10,7 @@ import rapidjson
from freqtrade.constants import FTHYPT_FILEVERSION
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer
from tests.conftest import log_has, log_has_re
from tests.conftest import CURRENT_TEST_STRATEGY, log_has, log_has_re
# Functions for recurrent object patching
@@ -168,9 +168,9 @@ def test__pprint_dict():
def test_get_strategy_filename(default_conf):
x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2')
x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV3')
assert isinstance(x, Path)
assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v2.py'
assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v3.py'
x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy')
assert x is None
@@ -178,7 +178,7 @@ def test_get_strategy_filename(default_conf):
def test_export_params(tmpdir):
filename = Path(tmpdir) / "StrategyTestV2.json"
filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json"
assert not filename.is_file()
params = {
"params_details": {
@@ -206,13 +206,13 @@ def test_export_params(tmpdir):
}
}
HyperoptTools.export_params(params, "StrategyTestV2", filename)
HyperoptTools.export_params(params, CURRENT_TEST_STRATEGY, filename)
assert filename.is_file()
with filename.open('r') as f:
content = rapidjson.load(f)
assert content['strategy_name'] == 'StrategyTestV2'
assert content['strategy_name'] == CURRENT_TEST_STRATEGY
assert 'params' in content
assert "buy" in content["params"]
assert "sell" in content["params"]
@@ -225,7 +225,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
default_conf['disableparamexport'] = False
export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params")
filename = Path(tmpdir) / "StrategyTestV2.json"
filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json"
assert not filename.is_file()
params = {
"params_details": {
@@ -254,17 +254,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
FTHYPT_FILEVERSION: 2,
}
HyperoptTools.try_export_params(default_conf, "StrategyTestV222", params)
HyperoptTools.try_export_params(default_conf, "StrategyTestVXXX", params)
assert log_has("Strategy not found, not exporting parameter file.", caplog)
assert export_mock.call_count == 0
caplog.clear()
HyperoptTools.try_export_params(default_conf, "StrategyTestV2", params)
HyperoptTools.try_export_params(default_conf, CURRENT_TEST_STRATEGY, params)
assert export_mock.call_count == 1
assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2'
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json'
assert export_mock.call_args_list[0][0][1] == CURRENT_TEST_STRATEGY
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v3.json'
def test_params_print(capsys):

View File

@@ -12,17 +12,17 @@ from freqtrade.data import history
from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data,
load_backtest_stats)
from freqtrade.edge import PairInfo
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats,
generate_daily_stats, generate_edge_table,
generate_pair_metrics,
generate_exit_reason_stats, generate_pair_metrics,
generate_periodic_breakdown_stats,
generate_sell_reason_stats,
generate_strategy_comparison,
generate_trading_stats, show_sorted_pairlist,
store_backtest_stats, text_table_bt_results,
text_table_sell_reason, text_table_strategy)
text_table_exit_reason, text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
from tests.conftest import CURRENT_TEST_STRATEGY
from tests.data.test_history import _backup_file, _clean_test_file
@@ -54,7 +54,7 @@ def test_text_table_bt_results():
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
default_conf.update({'strategy': 'StrategyTestV2'})
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
StrategyResolver.load_strategy(default_conf)
results = {'DefStrat': {
@@ -74,9 +74,10 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_EXIT]
}),
'config': default_conf,
'locks': [],
@@ -125,9 +126,10 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.ROI,
SellType.STOP_LOSS, SellType.FORCE_SELL]
"exit_reason": [ExitType.ROI, ExitType.ROI,
ExitType.STOP_LOSS, ExitType.FORCE_EXIT]
}),
'config': default_conf,
'locks': [],
@@ -262,7 +264,7 @@ def test_generate_trading_stats(testdatadir):
assert res['losses'] == 0
def test_text_table_sell_reason():
def test_text_table_exit_reason():
results = pd.DataFrame(
{
@@ -273,12 +275,12 @@ def test_text_table_sell_reason():
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
}
)
result_str = (
'| Sell Reason | Sells | Win Draws Loss Win% | Avg Profit % | Cum Profit % |'
'| Exit Reason | Exits | Win Draws Loss Win% | Avg Profit % | Cum Profit % |'
' Tot Profit BTC | Tot Profit % |\n'
'|---------------+---------+--------------------------+----------------+----------------+'
'------------------+----------------|\n'
@@ -288,9 +290,9 @@ def test_text_table_sell_reason():
' -0.2 | -5 |'
)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
results=results)
assert text_table_sell_reason(sell_reason_stats=sell_reason_stats,
assert text_table_exit_reason(exit_reason_stats=exit_reason_stats,
stake_currency='BTC') == result_str
@@ -305,23 +307,23 @@ def test_generate_sell_reason_stats():
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI.value, SellType.ROI.value, SellType.STOP_LOSS.value]
'exit_reason': [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value]
}
)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
results=results)
roi_result = sell_reason_stats[0]
assert roi_result['sell_reason'] == 'roi'
roi_result = exit_reason_stats[0]
assert roi_result['exit_reason'] == 'roi'
assert roi_result['trades'] == 2
assert pytest.approx(roi_result['profit_mean']) == 0.15
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
assert pytest.approx(roi_result['profit_mean']) == 0.15
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
stop_result = sell_reason_stats[1]
stop_result = exit_reason_stats[1]
assert stop_result['sell_reason'] == 'stop_loss'
assert stop_result['exit_reason'] == 'stop_loss'
assert stop_result['trades'] == 1
assert pytest.approx(stop_result['profit_mean']) == -0.1
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
@@ -397,6 +399,6 @@ def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
show_sorted_pairlist(default_conf, bt_data)
out, err = capsys.readouterr()
assert 'Pairs for Strategy StrategyTestV2: \n[' in out
assert 'Pairs for Strategy StrategyTestV3: \n[' in out
assert 'TOTAL' not in out
assert '"ETH/BTC", // ' in out