2020-03-15 14:36:53 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
2020-01-02 06:33:55 +00:00
|
|
|
import pandas as pd
|
2020-05-25 05:14:21 +00:00
|
|
|
import pytest
|
2020-03-15 14:36:53 +00:00
|
|
|
from arrow import Arrow
|
2020-01-02 06:33:55 +00:00
|
|
|
|
2020-01-09 05:52:34 +00:00
|
|
|
from freqtrade.edge import PairInfo
|
|
|
|
from freqtrade.optimize.optimize_reports import (
|
2020-05-25 17:50:09 +00:00
|
|
|
generate_pair_results, generate_edge_table, generate_sell_reason_stats,
|
2020-05-25 17:55:02 +00:00
|
|
|
generate_text_table, generate_text_table_sell_reason, generate_strategy_summary,
|
2020-05-25 17:34:46 +00:00
|
|
|
generate_text_table_strategy, store_backtest_result)
|
2020-01-02 06:33:55 +00:00
|
|
|
from freqtrade.strategy.interface import SellType
|
2020-03-15 14:36:53 +00:00
|
|
|
from tests.conftest import patch_exchange
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_generate_text_table(default_conf, mocker):
|
|
|
|
|
|
|
|
results = pd.DataFrame(
|
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.1, 0.2],
|
|
|
|
'profit_abs': [0.2, 0.4],
|
|
|
|
'trade_duration': [10, 30],
|
2020-02-07 02:51:50 +00:00
|
|
|
'wins': [2, 0],
|
|
|
|
'draws': [0, 0],
|
|
|
|
'losses': [0, 0]
|
2020-01-02 06:33:55 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
result_str = (
|
2020-02-27 12:28:28 +00:00
|
|
|
'| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
|
|
|
|
' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
|
|
|
'|---------+--------+----------------+----------------+------------------+'
|
|
|
|
'----------------+----------------+--------+---------+----------|\n'
|
2020-02-07 02:54:47 +00:00
|
|
|
'| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 |'
|
2020-02-27 12:28:28 +00:00
|
|
|
' 15.00 | 0:20:00 | 2 | 0 | 0 |\n'
|
|
|
|
'| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |'
|
|
|
|
' 15.00 | 0:20:00 | 2 | 0 | 0 |'
|
2020-01-02 06:33:55 +00:00
|
|
|
)
|
2020-05-25 17:34:46 +00:00
|
|
|
|
2020-05-25 17:50:09 +00:00
|
|
|
pair_results = generate_pair_results(data={'ETH/BTC': {}}, stake_currency='BTC',
|
|
|
|
max_open_trades=2, results=results)
|
2020-05-25 17:34:46 +00:00
|
|
|
assert generate_text_table(pair_results,
|
|
|
|
stake_currency='BTC') == result_str
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
|
2020-05-25 17:50:09 +00:00
|
|
|
def test_generate_pair_results(default_conf, mocker):
|
|
|
|
|
|
|
|
results = pd.DataFrame(
|
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.1, 0.2],
|
|
|
|
'profit_abs': [0.2, 0.4],
|
|
|
|
'trade_duration': [10, 30],
|
|
|
|
'wins': [2, 0],
|
|
|
|
'draws': [0, 0],
|
|
|
|
'losses': [0, 0]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
pair_results = generate_pair_results(data={'ETH/BTC': {}}, stake_currency='BTC',
|
|
|
|
max_open_trades=2, results=results)
|
|
|
|
assert isinstance(pair_results, list)
|
|
|
|
assert len(pair_results) == 2
|
|
|
|
assert pair_results[-1]['key'] == 'TOTAL'
|
|
|
|
assert (
|
|
|
|
pytest.approx(pair_results[-1]['profit_mean_pct']) == pair_results[-1]['profit_mean'] * 100)
|
|
|
|
assert (
|
|
|
|
pytest.approx(pair_results[-1]['profit_sum_pct']) == pair_results[-1]['profit_sum'] * 100)
|
|
|
|
|
|
|
|
|
2020-05-25 05:14:21 +00:00
|
|
|
def test_generate_text_table_sell_reason(default_conf):
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
results = pd.DataFrame(
|
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
2020-01-09 05:46:39 +00:00
|
|
|
'profit_percent': [0.1, 0.2, -0.1],
|
|
|
|
'profit_abs': [0.2, 0.4, -0.2],
|
2020-01-02 06:33:55 +00:00
|
|
|
'trade_duration': [10, 30, 10],
|
2020-02-07 02:51:50 +00:00
|
|
|
'wins': [2, 0, 0],
|
|
|
|
'draws': [0, 0, 0],
|
|
|
|
'losses': [0, 0, 1],
|
2020-01-02 06:33:55 +00:00
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
result_str = (
|
2020-02-27 12:28:28 +00:00
|
|
|
'| Sell Reason | Sells | Wins | Draws | Losses |'
|
2020-02-07 02:54:47 +00:00
|
|
|
' Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |\n'
|
2020-02-27 12:28:28 +00:00
|
|
|
'|---------------+---------+--------+---------+----------+'
|
|
|
|
'----------------+----------------+------------------+----------------|\n'
|
|
|
|
'| roi | 2 | 2 | 0 | 0 |'
|
2020-02-07 02:54:47 +00:00
|
|
|
' 15 | 30 | 0.6 | 15 |\n'
|
2020-02-27 12:28:28 +00:00
|
|
|
'| stop_loss | 1 | 0 | 0 | 1 |'
|
2020-02-07 02:54:47 +00:00
|
|
|
' -10 | -10 | -0.2 | -5 |'
|
2020-01-02 06:33:55 +00:00
|
|
|
)
|
2020-05-25 05:08:15 +00:00
|
|
|
|
|
|
|
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
|
|
|
|
results=results)
|
|
|
|
assert generate_text_table_sell_reason(sell_reason_stats=sell_reason_stats,
|
|
|
|
stake_currency='BTC') == result_str
|
2020-01-02 06:33:55 +00:00
|
|
|
|
|
|
|
|
2020-05-25 05:14:21 +00:00
|
|
|
def test_generate_sell_reason_stats(default_conf):
|
|
|
|
|
|
|
|
results = pd.DataFrame(
|
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.1, 0.2, -0.1],
|
|
|
|
'profit_abs': [0.2, 0.4, -0.2],
|
|
|
|
'trade_duration': [10, 30, 10],
|
|
|
|
'wins': [2, 0, 0],
|
|
|
|
'draws': [0, 0, 0],
|
|
|
|
'losses': [0, 0, 1],
|
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
|
|
|
|
results=results)
|
|
|
|
roi_result = sell_reason_stats[0]
|
|
|
|
assert roi_result['sell_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]
|
|
|
|
|
|
|
|
assert stop_result['sell_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)
|
|
|
|
assert pytest.approx(stop_result['profit_mean']) == -0.1
|
|
|
|
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
|
|
|
|
|
|
|
|
|
2020-01-02 06:33:55 +00:00
|
|
|
def test_generate_text_table_strategy(default_conf, mocker):
|
|
|
|
results = {}
|
2020-02-07 02:51:50 +00:00
|
|
|
results['TestStrategy1'] = pd.DataFrame(
|
2020-01-02 06:33:55 +00:00
|
|
|
{
|
|
|
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.1, 0.2, 0.3],
|
|
|
|
'profit_abs': [0.2, 0.4, 0.5],
|
|
|
|
'trade_duration': [10, 30, 10],
|
2020-02-07 02:51:50 +00:00
|
|
|
'wins': [2, 0, 0],
|
|
|
|
'draws': [0, 0, 0],
|
|
|
|
'losses': [0, 0, 1],
|
2020-01-02 06:33:55 +00:00
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}
|
|
|
|
)
|
2020-02-07 02:51:50 +00:00
|
|
|
results['TestStrategy2'] = pd.DataFrame(
|
2020-01-02 06:33:55 +00:00
|
|
|
{
|
|
|
|
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
|
|
|
|
'profit_percent': [0.4, 0.2, 0.3],
|
|
|
|
'profit_abs': [0.4, 0.4, 0.5],
|
|
|
|
'trade_duration': [15, 30, 15],
|
2020-02-07 02:51:50 +00:00
|
|
|
'wins': [4, 1, 0],
|
|
|
|
'draws': [0, 0, 0],
|
|
|
|
'losses': [0, 0, 1],
|
2020-01-02 06:33:55 +00:00
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
result_str = (
|
2020-02-27 12:28:28 +00:00
|
|
|
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot'
|
|
|
|
' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
|
|
|
'|---------------+--------+----------------+----------------+------------------+'
|
|
|
|
'----------------+----------------+--------+---------+----------|\n'
|
|
|
|
'| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |'
|
|
|
|
' 30.00 | 0:17:00 | 3 | 0 | 0 |\n'
|
|
|
|
'| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |'
|
|
|
|
' 45.00 | 0:20:00 | 3 | 0 | 0 |'
|
2020-01-02 06:33:55 +00:00
|
|
|
)
|
2020-05-25 17:55:02 +00:00
|
|
|
|
|
|
|
strategy_results = generate_strategy_summary(stake_currency='BTC',
|
|
|
|
max_open_trades=2,
|
|
|
|
all_results=results)
|
|
|
|
|
|
|
|
assert generate_text_table_strategy(strategy_results, 'BTC') == result_str
|
2020-01-09 05:52:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_generate_edge_table(edge_conf, mocker):
|
|
|
|
|
|
|
|
results = {}
|
|
|
|
results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
|
2020-02-27 12:28:28 +00:00
|
|
|
assert generate_edge_table(results).count('+') == 7
|
2020-01-09 05:52:34 +00:00
|
|
|
assert generate_edge_table(results).count('| ETH/BTC |') == 1
|
|
|
|
assert generate_edge_table(results).count(
|
2020-02-07 02:51:50 +00:00
|
|
|
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
|
2020-03-15 14:36:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_backtest_record(default_conf, fee, mocker):
|
|
|
|
names = []
|
|
|
|
records = []
|
|
|
|
patch_exchange(mocker)
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
|
|
|
mocker.patch(
|
|
|
|
'freqtrade.optimize.optimize_reports.file_dump_json',
|
|
|
|
new=lambda n, r: (names.append(n), records.append(r))
|
|
|
|
)
|
|
|
|
|
|
|
|
results = {'DefStrat': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
|
2020-03-15 14:40:12 +00:00
|
|
|
"UNITTEST/BTC", "UNITTEST/BTC"],
|
|
|
|
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
|
|
|
|
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
|
|
|
|
"open_time": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
|
|
|
|
Arrow(2017, 11, 14, 21, 36, 00).datetime,
|
|
|
|
Arrow(2017, 11, 14, 22, 12, 00).datetime,
|
|
|
|
Arrow(2017, 11, 14, 22, 44, 00).datetime],
|
|
|
|
"close_time": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
|
|
|
|
Arrow(2017, 11, 14, 22, 10, 00).datetime,
|
|
|
|
Arrow(2017, 11, 14, 22, 43, 00).datetime,
|
|
|
|
Arrow(2017, 11, 14, 22, 58, 00).datetime],
|
|
|
|
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
|
|
|
|
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
|
|
|
|
"open_index": [1, 119, 153, 185],
|
|
|
|
"close_index": [118, 151, 184, 199],
|
|
|
|
"trade_duration": [123, 34, 31, 14],
|
|
|
|
"open_at_end": [False, False, False, True],
|
|
|
|
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
|
|
|
|
SellType.ROI, SellType.FORCE_SELL]
|
|
|
|
})}
|
2020-03-15 14:36:53 +00:00
|
|
|
store_backtest_result(Path("backtest-result.json"), results)
|
|
|
|
# Assert file_dump_json was only called once
|
|
|
|
assert names == [Path('backtest-result.json')]
|
|
|
|
records = records[0]
|
|
|
|
# Ensure records are of correct type
|
|
|
|
assert len(records) == 4
|
|
|
|
|
|
|
|
# reset test to test with strategy name
|
|
|
|
names = []
|
|
|
|
records = []
|
2020-04-02 15:29:18 +00:00
|
|
|
results['Strat'] = results['DefStrat']
|
|
|
|
results['Strat2'] = results['DefStrat']
|
2020-03-15 14:36:53 +00:00
|
|
|
store_backtest_result(Path("backtest-result.json"), results)
|
2020-04-02 15:29:18 +00:00
|
|
|
assert names == [
|
|
|
|
Path('backtest-result-DefStrat.json'),
|
|
|
|
Path('backtest-result-Strat.json'),
|
|
|
|
Path('backtest-result-Strat2.json'),
|
|
|
|
]
|
2020-03-15 14:36:53 +00:00
|
|
|
records = records[0]
|
|
|
|
# Ensure records are of correct type
|
|
|
|
assert len(records) == 4
|
|
|
|
|
|
|
|
# ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117)
|
|
|
|
# Below follows just a typecheck of the schema/type of trade-records
|
|
|
|
oix = None
|
|
|
|
for (pair, profit, date_buy, date_sell, buy_index, dur,
|
|
|
|
openr, closer, open_at_end, sell_reason) in records:
|
|
|
|
assert pair == 'UNITTEST/BTC'
|
|
|
|
assert isinstance(profit, float)
|
|
|
|
# FIX: buy/sell should be converted to ints
|
|
|
|
assert isinstance(date_buy, float)
|
|
|
|
assert isinstance(date_sell, float)
|
|
|
|
assert isinstance(openr, float)
|
|
|
|
assert isinstance(closer, float)
|
|
|
|
assert isinstance(open_at_end, bool)
|
|
|
|
assert isinstance(sell_reason, str)
|
|
|
|
isinstance(buy_index, pd._libs.tslib.Timestamp)
|
|
|
|
if oix:
|
|
|
|
assert buy_index > oix
|
|
|
|
oix = buy_index
|
|
|
|
assert dur > 0
|