2018-11-09 19:51:15 +00:00
|
|
|
# pragma pylint: disable=missing-docstring, C0103, C0330
|
2018-11-07 23:22:46 +00:00
|
|
|
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
|
|
|
|
|
2018-11-09 19:51:15 +00:00
|
|
|
import logging
|
2018-12-13 05:26:04 +00:00
|
|
|
import math
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
|
2018-10-03 12:23:10 +00:00
|
|
|
import arrow
|
|
|
|
import numpy as np
|
2018-12-13 05:26:04 +00:00
|
|
|
import pytest
|
|
|
|
from pandas import DataFrame, to_datetime
|
2018-10-05 15:07:20 +00:00
|
|
|
|
2020-03-08 10:35:31 +00:00
|
|
|
from freqtrade.data.converter import ohlcv_to_dataframe
|
2018-12-13 05:26:04 +00:00
|
|
|
from freqtrade.edge import Edge, PairInfo
|
2021-06-08 19:06:47 +00:00
|
|
|
from freqtrade.enums import SellType
|
2020-09-28 17:43:15 +00:00
|
|
|
from freqtrade.exceptions import OperationalException
|
2019-09-08 07:54:15 +00:00
|
|
|
from tests.conftest import get_patched_freqtradebot, log_has
|
|
|
|
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
|
|
|
|
_get_frame_time_from_offset)
|
2018-10-02 14:07:33 +00:00
|
|
|
|
2020-09-28 17:43:15 +00:00
|
|
|
|
2018-10-02 14:07:33 +00:00
|
|
|
# Cases to be tested:
|
2018-10-25 15:24:33 +00:00
|
|
|
# 1) Open trade should be removed from the end
|
|
|
|
# 2) Two complete trades within dataframe (with sell hit for all)
|
|
|
|
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
|
|
|
# 4) Entered, sl 3%, candle drops 4%, recovers to 1% => Trade closed, 3% loss
|
2018-11-02 18:54:32 +00:00
|
|
|
# 5) Stoploss and sell are hit. should sell on stoploss
|
2018-10-03 08:37:36 +00:00
|
|
|
####################################################################
|
2018-10-02 14:07:33 +00:00
|
|
|
|
2020-03-08 10:35:31 +00:00
|
|
|
tests_start_time = arrow.get(2018, 10, 3)
|
2020-06-02 07:36:04 +00:00
|
|
|
timeframe_in_minute = 60
|
2018-10-03 12:23:10 +00:00
|
|
|
|
2019-05-23 17:48:22 +00:00
|
|
|
# Helpers for this test file
|
2018-10-03 12:23:10 +00:00
|
|
|
|
2019-05-23 17:48:22 +00:00
|
|
|
|
|
|
|
def _validate_ohlc(buy_ohlc_sell_matrice):
|
|
|
|
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
|
|
|
|
# if not high < open < low or not high < close < low
|
|
|
|
if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]:
|
|
|
|
raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!')
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def _build_dataframe(buy_ohlc_sell_matrice):
|
|
|
|
_validate_ohlc(buy_ohlc_sell_matrice)
|
2020-03-08 10:35:31 +00:00
|
|
|
data = []
|
2019-05-23 17:48:22 +00:00
|
|
|
for ohlc in buy_ohlc_sell_matrice:
|
2020-03-08 10:35:31 +00:00
|
|
|
d = {
|
|
|
|
'date': tests_start_time.shift(
|
2019-05-23 17:48:22 +00:00
|
|
|
minutes=(
|
|
|
|
ohlc[0] *
|
2020-10-12 17:58:04 +00:00
|
|
|
timeframe_in_minute)).int_timestamp *
|
2019-05-23 17:48:22 +00:00
|
|
|
1000,
|
|
|
|
'buy': ohlc[1],
|
|
|
|
'open': ohlc[2],
|
|
|
|
'high': ohlc[3],
|
|
|
|
'low': ohlc[4],
|
|
|
|
'close': ohlc[5],
|
|
|
|
'sell': ohlc[6]}
|
2020-03-08 10:35:31 +00:00
|
|
|
data.append(d)
|
2019-05-23 17:48:22 +00:00
|
|
|
|
2020-03-08 10:35:31 +00:00
|
|
|
frame = DataFrame(data)
|
2019-05-23 17:48:22 +00:00
|
|
|
frame['date'] = to_datetime(frame['date'],
|
|
|
|
unit='ms',
|
|
|
|
utc=True,
|
|
|
|
infer_datetime_format=True)
|
|
|
|
|
|
|
|
return frame
|
|
|
|
|
|
|
|
|
|
|
|
def _time_on_candle(number):
|
2020-03-08 10:35:31 +00:00
|
|
|
return np.datetime64(tests_start_time.shift(
|
2020-10-12 17:58:04 +00:00
|
|
|
minutes=(number * timeframe_in_minute)).int_timestamp * 1000, 'ms')
|
2019-05-23 17:48:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
# End helper functions
|
2018-11-09 19:51:15 +00:00
|
|
|
# Open trade should be removed from the end
|
|
|
|
tc0 = BTContainer(data=[
|
2021-07-20 16:40:32 +00:00
|
|
|
# D O H L C V B S SN
|
|
|
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''],
|
|
|
|
[1, 5000, 5025, 4975, 4987, 6172, 0, 1, '']], # enter trade (signal on last candle)
|
2019-10-05 08:40:59 +00:00
|
|
|
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
|
2018-11-09 19:51:15 +00:00
|
|
|
trades=[]
|
|
|
|
)
|
|
|
|
|
|
|
|
# Two complete trades within dataframe(with sell hit for all)
|
|
|
|
tc1 = BTContainer(data=[
|
2021-07-20 16:40:32 +00:00
|
|
|
# D O H L C V B S SN
|
|
|
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''],
|
|
|
|
[1, 5000, 5025, 4975, 4987, 6172, 0, 1, ''], # enter trade (signal on last candle)
|
|
|
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # exit at open
|
|
|
|
[3, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], # no action
|
|
|
|
[4, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # should enter the trade
|
|
|
|
[5, 5000, 5025, 4975, 4987, 6172, 0, 1, ''], # no action
|
|
|
|
[6, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # should sell
|
2018-11-09 19:51:15 +00:00
|
|
|
],
|
2019-10-05 08:40:59 +00:00
|
|
|
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
|
2018-11-09 19:51:15 +00:00
|
|
|
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
|
|
|
|
BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
|
|
|
|
)
|
|
|
|
|
|
|
|
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
|
|
|
tc2 = BTContainer(data=[
|
2021-07-20 16:40:32 +00:00
|
|
|
# D O H L C V B S SN
|
|
|
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''],
|
|
|
|
[1, 5000, 5025, 4600, 4987, 6172, 0, 0, ''], # enter trade, stoploss hit
|
|
|
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''],
|
2018-11-09 19:51:15 +00:00
|
|
|
],
|
2019-10-05 08:40:59 +00:00
|
|
|
stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01,
|
2018-11-09 19:51:15 +00:00
|
|
|
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
|
|
|
)
|
|
|
|
|
|
|
|
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
|
|
|
|
tc3 = BTContainer(data=[
|
2021-07-20 16:40:32 +00:00
|
|
|
# D O H L C V B S SN
|
|
|
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''],
|
|
|
|
[1, 5000, 5025, 4800, 4987, 6172, 0, 0, ''], # enter trade, stoploss hit
|
|
|
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''],
|
2018-11-09 19:51:15 +00:00
|
|
|
],
|
2019-10-05 08:40:59 +00:00
|
|
|
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
|
2018-11-09 19:51:15 +00:00
|
|
|
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
|
|
|
)
|
|
|
|
|
2018-11-10 16:20:11 +00:00
|
|
|
# 5) Stoploss and sell are hit. should sell on stoploss
|
|
|
|
tc4 = BTContainer(data=[
|
2021-07-20 16:40:32 +00:00
|
|
|
# D O H L C V B S SN
|
|
|
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''],
|
|
|
|
[1, 5000, 5025, 4800, 4987, 6172, 0, 1, ''], # enter trade, stoploss hit, sell signal
|
|
|
|
[2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''],
|
2018-11-09 19:51:15 +00:00
|
|
|
],
|
2019-10-05 08:40:59 +00:00
|
|
|
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
|
2018-11-10 16:20:11 +00:00
|
|
|
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
2018-11-09 19:51:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
TESTS = [
|
|
|
|
tc0,
|
|
|
|
tc1,
|
|
|
|
tc2,
|
|
|
|
tc3,
|
|
|
|
tc4
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("data", TESTS)
|
|
|
|
def test_edge_results(edge_conf, mocker, caplog, data) -> None:
|
|
|
|
"""
|
|
|
|
run functional tests
|
|
|
|
"""
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
frame = _build_backtest_dataframe(data.data)
|
|
|
|
caplog.set_level(logging.DEBUG)
|
|
|
|
edge.fee = 0
|
|
|
|
|
|
|
|
trades = edge._find_trades_for_stoploss_range(frame, 'TEST/BTC', [data.stop_loss])
|
|
|
|
results = edge._fill_calculable_fields(DataFrame(trades)) if trades else DataFrame()
|
|
|
|
|
|
|
|
assert len(trades) == len(data.trades)
|
|
|
|
|
|
|
|
if not results.empty:
|
2020-02-28 09:36:39 +00:00
|
|
|
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
|
2018-11-09 19:51:15 +00:00
|
|
|
|
|
|
|
for c, trade in enumerate(data.trades):
|
|
|
|
res = results.iloc[c]
|
|
|
|
assert res.exit_type == trade.sell_reason
|
2020-06-26 07:21:28 +00:00
|
|
|
assert res.open_date == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None)
|
|
|
|
assert res.close_date == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None)
|
2018-11-09 19:51:15 +00:00
|
|
|
|
|
|
|
|
2018-11-27 13:02:34 +00:00
|
|
|
def test_adjust(mocker, edge_conf):
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
2018-10-02 14:07:33 +00:00
|
|
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
2018-11-04 17:11:58 +00:00
|
|
|
return_value={
|
2018-11-21 23:04:20 +00:00
|
|
|
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
|
|
|
'C/D': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
|
|
|
'N/O': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60)
|
2018-11-04 17:11:58 +00:00
|
|
|
}
|
2018-10-02 14:07:33 +00:00
|
|
|
))
|
|
|
|
|
|
|
|
pairs = ['A/B', 'C/D', 'E/F', 'G/H']
|
2018-11-07 18:24:53 +00:00
|
|
|
assert(edge.adjust(pairs) == ['E/F', 'C/D'])
|
2018-10-02 16:05:24 +00:00
|
|
|
|
|
|
|
|
2018-11-27 13:02:34 +00:00
|
|
|
def test_stoploss(mocker, edge_conf):
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
2018-10-04 16:51:59 +00:00
|
|
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
2018-11-04 17:11:58 +00:00
|
|
|
return_value={
|
2018-11-21 23:04:20 +00:00
|
|
|
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
|
|
|
'C/D': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
|
|
|
'N/O': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60)
|
2018-11-04 17:11:58 +00:00
|
|
|
}
|
2018-10-04 16:51:59 +00:00
|
|
|
))
|
|
|
|
|
|
|
|
assert edge.stoploss('E/F') == -0.01
|
|
|
|
|
|
|
|
|
2018-11-30 16:59:51 +00:00
|
|
|
def test_nonexisting_stoploss(mocker, edge_conf):
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
2018-11-29 17:31:08 +00:00
|
|
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
|
|
|
return_value={
|
|
|
|
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
|
|
|
}
|
|
|
|
))
|
|
|
|
|
|
|
|
assert edge.stoploss('N/O') == -0.1
|
|
|
|
|
2018-11-29 17:45:37 +00:00
|
|
|
|
2021-02-11 16:09:31 +00:00
|
|
|
def test_edge_stake_amount(mocker, edge_conf):
|
2018-11-30 16:59:51 +00:00
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
|
|
|
return_value={
|
|
|
|
'E/F': PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
|
|
|
}
|
|
|
|
))
|
2021-02-11 16:09:31 +00:00
|
|
|
assert edge._capital_ratio == 0.5
|
|
|
|
assert edge.stake_amount('E/F', free_capital=100, total_capital=100,
|
|
|
|
capital_in_trade=25) == 31.25
|
|
|
|
|
|
|
|
assert edge.stake_amount('E/F', free_capital=20, total_capital=100,
|
|
|
|
capital_in_trade=25) == 20
|
|
|
|
|
|
|
|
assert edge.stake_amount('E/F', free_capital=0, total_capital=100,
|
|
|
|
capital_in_trade=25) == 0
|
|
|
|
|
|
|
|
# Test with increased allowed_risk
|
|
|
|
# Result should be no more than allowed capital
|
|
|
|
edge._allowed_risk = 0.4
|
|
|
|
edge._capital_ratio = 0.5
|
|
|
|
assert edge.stake_amount('E/F', free_capital=100, total_capital=100,
|
|
|
|
capital_in_trade=25) == 62.5
|
|
|
|
|
|
|
|
assert edge.stake_amount('E/F', free_capital=100, total_capital=100,
|
|
|
|
capital_in_trade=0) == 50
|
|
|
|
|
|
|
|
edge._capital_ratio = 1
|
|
|
|
# Full capital is available
|
|
|
|
assert edge.stake_amount('E/F', free_capital=100, total_capital=100,
|
|
|
|
capital_in_trade=0) == 100
|
|
|
|
# Full capital is available
|
|
|
|
assert edge.stake_amount('E/F', free_capital=0, total_capital=100,
|
|
|
|
capital_in_trade=0) == 0
|
2018-11-30 16:59:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_nonexisting_stake_amount(mocker, edge_conf):
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
|
|
|
return_value={
|
2018-12-01 10:56:16 +00:00
|
|
|
'E/F': PairInfo(-0.11, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
2018-11-30 16:59:51 +00:00
|
|
|
}
|
|
|
|
))
|
2018-12-04 16:13:46 +00:00
|
|
|
# should use strategy stoploss
|
|
|
|
assert edge.stake_amount('N/O', 1, 2, 1) == 0.15
|
2018-11-30 16:59:51 +00:00
|
|
|
|
|
|
|
|
2018-11-09 19:51:15 +00:00
|
|
|
def test_edge_heartbeat_calculate(mocker, edge_conf):
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
heartbeat = edge_conf['edge']['process_throttle_secs']
|
2018-10-05 15:07:20 +00:00
|
|
|
|
|
|
|
# should not recalculate if heartbeat not reached
|
2020-10-12 17:58:04 +00:00
|
|
|
edge._last_updated = arrow.utcnow().int_timestamp - heartbeat + 1
|
2018-10-05 15:07:20 +00:00
|
|
|
|
2021-03-30 18:20:24 +00:00
|
|
|
assert edge.calculate(edge_conf['exchange']['pair_whitelist']) is False
|
2018-10-05 15:07:20 +00:00
|
|
|
|
|
|
|
|
2019-12-17 10:43:42 +00:00
|
|
|
def mocked_load_data(datadir, pairs=[], timeframe='0m',
|
|
|
|
timerange=None, *args, **kwargs):
|
2018-10-05 15:07:20 +00:00
|
|
|
hz = 0.1
|
|
|
|
base = 0.001
|
|
|
|
|
2019-06-15 11:47:20 +00:00
|
|
|
NEOBTC = [
|
2018-10-05 15:07:20 +00:00
|
|
|
[
|
2020-10-12 17:58:04 +00:00
|
|
|
tests_start_time.shift(minutes=(x * timeframe_in_minute)).int_timestamp * 1000,
|
2018-10-25 14:59:05 +00:00
|
|
|
math.sin(x * hz) / 1000 + base,
|
2018-10-05 15:07:20 +00:00
|
|
|
math.sin(x * hz) / 1000 + base + 0.0001,
|
|
|
|
math.sin(x * hz) / 1000 + base - 0.0001,
|
|
|
|
math.sin(x * hz) / 1000 + base,
|
|
|
|
123.45
|
|
|
|
] for x in range(0, 500)]
|
|
|
|
|
|
|
|
hz = 0.2
|
|
|
|
base = 0.002
|
|
|
|
LTCBTC = [
|
|
|
|
[
|
2020-10-12 17:58:04 +00:00
|
|
|
tests_start_time.shift(minutes=(x * timeframe_in_minute)).int_timestamp * 1000,
|
2018-10-25 14:59:05 +00:00
|
|
|
math.sin(x * hz) / 1000 + base,
|
2018-10-05 15:07:20 +00:00
|
|
|
math.sin(x * hz) / 1000 + base + 0.0001,
|
|
|
|
math.sin(x * hz) / 1000 + base - 0.0001,
|
|
|
|
math.sin(x * hz) / 1000 + base,
|
|
|
|
123.45
|
|
|
|
] for x in range(0, 500)]
|
|
|
|
|
2020-03-08 10:35:31 +00:00
|
|
|
pairdata = {'NEO/BTC': ohlcv_to_dataframe(NEOBTC, '1h', pair="NEO/BTC",
|
|
|
|
fill_missing=True),
|
|
|
|
'LTC/BTC': ohlcv_to_dataframe(LTCBTC, '1h', pair="LTC/BTC",
|
|
|
|
fill_missing=True)}
|
2018-10-05 15:07:20 +00:00
|
|
|
return pairdata
|
|
|
|
|
|
|
|
|
2018-11-27 13:02:34 +00:00
|
|
|
def test_edge_process_downloaded_data(mocker, edge_conf):
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
2018-10-05 15:07:20 +00:00
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
2020-03-20 01:21:17 +00:00
|
|
|
mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock())
|
|
|
|
mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
|
2018-11-27 13:02:34 +00:00
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
2018-10-05 15:07:20 +00:00
|
|
|
|
2021-03-30 18:20:24 +00:00
|
|
|
assert edge.calculate(edge_conf['exchange']['pair_whitelist'])
|
2018-10-05 15:07:20 +00:00
|
|
|
assert len(edge._cached_pairs) == 2
|
2020-10-12 17:58:04 +00:00
|
|
|
assert edge._last_updated <= arrow.utcnow().int_timestamp + 2
|
2018-10-05 15:07:20 +00:00
|
|
|
|
|
|
|
|
2019-05-23 17:48:22 +00:00
|
|
|
def test_edge_process_no_data(mocker, edge_conf, caplog):
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
2020-03-20 01:21:17 +00:00
|
|
|
mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock())
|
|
|
|
mocker.patch('freqtrade.edge.edge_positioning.load_data', MagicMock(return_value={}))
|
2019-05-23 17:48:22 +00:00
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
|
2021-03-30 18:20:24 +00:00
|
|
|
assert not edge.calculate(edge_conf['exchange']['pair_whitelist'])
|
2019-05-23 17:48:22 +00:00
|
|
|
assert len(edge._cached_pairs) == 0
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has("No data found. Edge is stopped ...", caplog)
|
2019-05-23 17:48:22 +00:00
|
|
|
assert edge._last_updated == 0
|
|
|
|
|
|
|
|
|
|
|
|
def test_edge_process_no_trades(mocker, edge_conf, caplog):
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
2021-04-23 04:50:39 +00:00
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001)
|
|
|
|
mocker.patch('freqtrade.edge.edge_positioning.refresh_data', )
|
2020-03-20 01:21:17 +00:00
|
|
|
mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
|
2019-05-23 17:48:22 +00:00
|
|
|
# Return empty
|
2021-04-23 04:50:39 +00:00
|
|
|
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', return_value=[])
|
2019-05-23 17:48:22 +00:00
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
|
2021-03-30 18:20:24 +00:00
|
|
|
assert not edge.calculate(edge_conf['exchange']['pair_whitelist'])
|
2019-05-23 17:48:22 +00:00
|
|
|
assert len(edge._cached_pairs) == 0
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has("No trades found.", caplog)
|
2019-05-23 17:48:22 +00:00
|
|
|
|
|
|
|
|
2021-04-23 04:50:39 +00:00
|
|
|
def test_edge_process_no_pairs(mocker, edge_conf, caplog):
|
|
|
|
edge_conf['exchange']['pair_whitelist'] = []
|
2021-05-04 05:46:30 +00:00
|
|
|
mocker.patch('freqtrade.freqtradebot.validate_config_consistency')
|
|
|
|
|
2021-04-23 04:50:39 +00:00
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001)
|
|
|
|
mocker.patch('freqtrade.edge.edge_positioning.refresh_data')
|
|
|
|
mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
|
|
|
|
# Return empty
|
|
|
|
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', return_value=[])
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
assert fee_mock.call_count == 0
|
|
|
|
assert edge.fee is None
|
|
|
|
|
|
|
|
assert not edge.calculate(['XRP/USDT'])
|
|
|
|
assert fee_mock.call_count == 1
|
|
|
|
assert edge.fee == 0.001
|
|
|
|
|
|
|
|
|
2019-05-23 17:48:22 +00:00
|
|
|
def test_edge_init_error(mocker, edge_conf,):
|
|
|
|
edge_conf['stake_amount'] = 0.5
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
|
|
|
with pytest.raises(OperationalException, match='Edge works only with unlimited stake amount'):
|
|
|
|
get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
|
|
|
|
|
2020-05-09 23:22:49 +00:00
|
|
|
@pytest.mark.parametrize("fee,risk_reward_ratio,expectancy", [
|
|
|
|
(0.0005, 306.5384615384, 101.5128205128),
|
|
|
|
(0.001, 152.6923076923, 50.2307692308),
|
|
|
|
])
|
|
|
|
def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectancy):
|
2018-11-09 19:51:15 +00:00
|
|
|
edge_conf['edge']['min_trade_number'] = 2
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
2018-10-03 12:23:10 +00:00
|
|
|
|
2019-12-14 12:22:42 +00:00
|
|
|
def get_fee(*args, **kwargs):
|
2020-05-09 23:22:49 +00:00
|
|
|
return fee
|
2018-10-03 12:23:10 +00:00
|
|
|
|
2018-11-07 23:22:46 +00:00
|
|
|
freqtrade.exchange.get_fee = get_fee
|
2018-11-09 19:51:15 +00:00
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
2018-10-03 12:23:10 +00:00
|
|
|
|
|
|
|
trades = [
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-06-26 07:21:28 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:05:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:10:00.000000000'),
|
2018-10-03 12:23:10 +00:00
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 17,
|
|
|
|
'close_rate': 17,
|
2018-11-02 17:10:03 +00:00
|
|
|
'exit_type': 'sell_signal'},
|
2018-10-03 12:23:10 +00:00
|
|
|
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-06-26 07:21:28 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
|
2018-10-03 12:23:10 +00:00
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 20,
|
|
|
|
'close_rate': 20,
|
|
|
|
'exit_type': 'sell_signal'},
|
|
|
|
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-06-26 07:21:28 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:30:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:40:00.000000000'),
|
2018-10-03 12:23:10 +00:00
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 26,
|
|
|
|
'close_rate': 34,
|
|
|
|
'exit_type': 'sell_signal'}
|
|
|
|
]
|
|
|
|
|
|
|
|
trades_df = DataFrame(trades)
|
|
|
|
trades_df = edge._fill_calculable_fields(trades_df)
|
|
|
|
final = edge._process_expectancy(trades_df)
|
|
|
|
assert len(final) == 1
|
2018-10-03 08:37:36 +00:00
|
|
|
|
2018-11-04 17:11:58 +00:00
|
|
|
assert 'TEST/BTC' in final
|
|
|
|
assert final['TEST/BTC'].stoploss == -0.9
|
|
|
|
assert round(final['TEST/BTC'].winrate, 10) == 0.3333333333
|
2020-05-09 23:22:49 +00:00
|
|
|
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == risk_reward_ratio
|
2018-11-04 17:11:58 +00:00
|
|
|
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
2020-05-09 23:22:49 +00:00
|
|
|
assert round(final['TEST/BTC'].expectancy, 10) == expectancy
|
2019-05-23 17:48:22 +00:00
|
|
|
|
|
|
|
# Pop last item so no trade is profitable
|
|
|
|
trades.pop()
|
|
|
|
trades_df = DataFrame(trades)
|
|
|
|
trades_df = edge._fill_calculable_fields(trades_df)
|
|
|
|
final = edge._process_expectancy(trades_df)
|
|
|
|
assert len(final) == 0
|
|
|
|
assert isinstance(final, dict)
|
2020-07-28 06:16:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
|
|
|
|
edge_conf['edge']['min_trade_number'] = 2
|
|
|
|
edge_conf['edge']['remove_pumps'] = True
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
|
|
|
|
freqtrade.exchange.get_fee = fee
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
|
|
|
|
trades = [
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-08-09 08:28:11 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:05:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:10:00.000000000'),
|
2020-07-28 06:16:55 +00:00
|
|
|
'open_index': 1,
|
|
|
|
'close_index': 1,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 17,
|
|
|
|
'close_rate': 15,
|
|
|
|
'exit_type': 'sell_signal'},
|
|
|
|
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-08-09 08:28:11 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
|
2020-07-28 06:16:55 +00:00
|
|
|
'open_index': 4,
|
|
|
|
'close_index': 4,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 20,
|
|
|
|
'close_rate': 10,
|
|
|
|
'exit_type': 'sell_signal'},
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-08-09 08:28:11 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
|
2020-07-28 06:16:55 +00:00
|
|
|
'open_index': 4,
|
|
|
|
'close_index': 4,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 20,
|
|
|
|
'close_rate': 10,
|
|
|
|
'exit_type': 'sell_signal'},
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-08-09 08:28:11 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
|
2020-07-28 06:16:55 +00:00
|
|
|
'open_index': 4,
|
|
|
|
'close_index': 4,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 20,
|
|
|
|
'close_rate': 10,
|
|
|
|
'exit_type': 'sell_signal'},
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-08-09 08:28:11 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
|
2020-07-28 06:16:55 +00:00
|
|
|
'open_index': 4,
|
|
|
|
'close_index': 4,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 20,
|
|
|
|
'close_rate': 10,
|
|
|
|
'exit_type': 'sell_signal'},
|
|
|
|
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
2020-08-09 08:28:11 +00:00
|
|
|
'open_date': np.datetime64('2018-10-03T00:30:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:40:00.000000000'),
|
2020-07-28 06:16:55 +00:00
|
|
|
'open_index': 6,
|
|
|
|
'close_index': 7,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 26,
|
|
|
|
'close_rate': 134,
|
|
|
|
'exit_type': 'sell_signal'}
|
|
|
|
]
|
|
|
|
|
|
|
|
trades_df = DataFrame(trades)
|
|
|
|
trades_df = edge._fill_calculable_fields(trades_df)
|
|
|
|
final = edge._process_expectancy(trades_df)
|
|
|
|
|
|
|
|
assert 'TEST/BTC' in final
|
|
|
|
assert final['TEST/BTC'].stoploss == -0.9
|
|
|
|
assert final['TEST/BTC'].nb_trades == len(trades_df) - 1
|
|
|
|
assert round(final['TEST/BTC'].winrate, 10) == 0.0
|
2020-10-09 04:47:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_process_expectancy_only_wins(mocker, edge_conf, fee,):
|
|
|
|
edge_conf['edge']['min_trade_number'] = 2
|
|
|
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
|
|
|
|
|
|
|
freqtrade.exchange.get_fee = fee
|
|
|
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
|
|
|
|
|
|
|
trades = [
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
|
|
|
'open_date': np.datetime64('2018-10-03T00:05:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:10:00.000000000'),
|
|
|
|
'open_index': 1,
|
|
|
|
'close_index': 1,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 15,
|
|
|
|
'close_rate': 17,
|
|
|
|
'exit_type': 'sell_signal'},
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
|
|
|
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
|
|
|
|
'open_index': 4,
|
|
|
|
'close_index': 4,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 10,
|
|
|
|
'close_rate': 20,
|
|
|
|
'exit_type': 'sell_signal'},
|
|
|
|
{'pair': 'TEST/BTC',
|
|
|
|
'stoploss': -0.9,
|
|
|
|
'profit_percent': '',
|
|
|
|
'profit_abs': '',
|
|
|
|
'open_date': np.datetime64('2018-10-03T00:30:00.000000000'),
|
|
|
|
'close_date': np.datetime64('2018-10-03T00:40:00.000000000'),
|
|
|
|
'open_index': 6,
|
|
|
|
'close_index': 7,
|
|
|
|
'trade_duration': '',
|
|
|
|
'open_rate': 26,
|
|
|
|
'close_rate': 134,
|
|
|
|
'exit_type': 'sell_signal'}
|
|
|
|
]
|
|
|
|
|
|
|
|
trades_df = DataFrame(trades)
|
|
|
|
trades_df = edge._fill_calculable_fields(trades_df)
|
|
|
|
final = edge._process_expectancy(trades_df)
|
|
|
|
|
|
|
|
assert 'TEST/BTC' in final
|
|
|
|
assert final['TEST/BTC'].stoploss == -0.9
|
|
|
|
assert final['TEST/BTC'].nb_trades == len(trades_df)
|
|
|
|
assert round(final['TEST/BTC'].winrate, 10) == 1.0
|
|
|
|
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == float('inf')
|
|
|
|
assert round(final['TEST/BTC'].expectancy, 10) == float('inf')
|