commit
b7686d06a7
@ -405,7 +405,7 @@ class Arguments(object):
|
|||||||
raise Exception('Incorrect syntax for timerange "%s"' % text)
|
raise Exception('Incorrect syntax for timerange "%s"' % text)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_int_positive(value) -> int:
|
def check_int_positive(value: str) -> int:
|
||||||
try:
|
try:
|
||||||
uint = int(value)
|
uint = int(value)
|
||||||
if uint <= 0:
|
if uint <= 0:
|
||||||
|
@ -10,10 +10,11 @@ import numpy as np
|
|||||||
import pytest
|
import pytest
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
|
from freqtrade import OperationalException
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.edge import Edge, PairInfo
|
from freqtrade.edge import Edge, PairInfo
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
from freqtrade.tests.conftest import get_patched_freqtradebot
|
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has
|
||||||
from freqtrade.tests.optimize import (BTContainer, BTrade,
|
from freqtrade.tests.optimize import (BTContainer, BTrade,
|
||||||
_build_backtest_dataframe,
|
_build_backtest_dataframe,
|
||||||
_get_frame_time_from_offset)
|
_get_frame_time_from_offset)
|
||||||
@ -30,7 +31,50 @@ ticker_start_time = arrow.get(2018, 10, 3)
|
|||||||
ticker_interval_in_minute = 60
|
ticker_interval_in_minute = 60
|
||||||
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
|
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
|
||||||
|
|
||||||
|
# Helpers for this test file
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
tickers = []
|
||||||
|
for ohlc in buy_ohlc_sell_matrice:
|
||||||
|
ticker = {
|
||||||
|
'date': ticker_start_time.shift(
|
||||||
|
minutes=(
|
||||||
|
ohlc[0] *
|
||||||
|
ticker_interval_in_minute)).timestamp *
|
||||||
|
1000,
|
||||||
|
'buy': ohlc[1],
|
||||||
|
'open': ohlc[2],
|
||||||
|
'high': ohlc[3],
|
||||||
|
'low': ohlc[4],
|
||||||
|
'close': ohlc[5],
|
||||||
|
'sell': ohlc[6]}
|
||||||
|
tickers.append(ticker)
|
||||||
|
|
||||||
|
frame = DataFrame(tickers)
|
||||||
|
frame['date'] = to_datetime(frame['date'],
|
||||||
|
unit='ms',
|
||||||
|
utc=True,
|
||||||
|
infer_datetime_format=True)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
|
||||||
|
def _time_on_candle(number):
|
||||||
|
return np.datetime64(ticker_start_time.shift(
|
||||||
|
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
|
||||||
|
|
||||||
|
|
||||||
|
# End helper functions
|
||||||
# Open trade should be removed from the end
|
# Open trade should be removed from the end
|
||||||
tc0 = BTContainer(data=[
|
tc0 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
@ -203,46 +247,6 @@ def test_nonexisting_stake_amount(mocker, edge_conf):
|
|||||||
assert edge.stake_amount('N/O', 1, 2, 1) == 0.15
|
assert edge.stake_amount('N/O', 1, 2, 1) == 0.15
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
tickers = []
|
|
||||||
for ohlc in buy_ohlc_sell_matrice:
|
|
||||||
ticker = {
|
|
||||||
'date': ticker_start_time.shift(
|
|
||||||
minutes=(
|
|
||||||
ohlc[0] *
|
|
||||||
ticker_interval_in_minute)).timestamp *
|
|
||||||
1000,
|
|
||||||
'buy': ohlc[1],
|
|
||||||
'open': ohlc[2],
|
|
||||||
'high': ohlc[3],
|
|
||||||
'low': ohlc[4],
|
|
||||||
'close': ohlc[5],
|
|
||||||
'sell': ohlc[6]}
|
|
||||||
tickers.append(ticker)
|
|
||||||
|
|
||||||
frame = DataFrame(tickers)
|
|
||||||
frame['date'] = to_datetime(frame['date'],
|
|
||||||
unit='ms',
|
|
||||||
utc=True,
|
|
||||||
infer_datetime_format=True)
|
|
||||||
|
|
||||||
return frame
|
|
||||||
|
|
||||||
|
|
||||||
def _time_on_candle(number):
|
|
||||||
return np.datetime64(ticker_start_time.shift(
|
|
||||||
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
|
|
||||||
|
|
||||||
|
|
||||||
def test_edge_heartbeat_calculate(mocker, edge_conf):
|
def test_edge_heartbeat_calculate(mocker, edge_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
@ -298,6 +302,40 @@ def test_edge_process_downloaded_data(mocker, edge_conf):
|
|||||||
assert edge._last_updated <= arrow.utcnow().timestamp + 2
|
assert edge._last_updated <= arrow.utcnow().timestamp + 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_process_no_data(mocker, edge_conf, caplog):
|
||||||
|
edge_conf['datadir'] = None
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||||
|
mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={}))
|
||||||
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
|
||||||
|
assert not edge.calculate()
|
||||||
|
assert len(edge._cached_pairs) == 0
|
||||||
|
assert log_has("No data found. Edge is stopped ...", caplog.record_tuples)
|
||||||
|
assert edge._last_updated == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_process_no_trades(mocker, edge_conf, caplog):
|
||||||
|
edge_conf['datadir'] = None
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||||
|
mocker.patch('freqtrade.data.history.load_data', mocked_load_data)
|
||||||
|
# Return empty
|
||||||
|
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', MagicMock(return_value=[]))
|
||||||
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
|
||||||
|
assert not edge.calculate()
|
||||||
|
assert len(edge._cached_pairs) == 0
|
||||||
|
assert log_has("No trades found.", caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
def test_process_expectancy(mocker, edge_conf):
|
def test_process_expectancy(mocker, edge_conf):
|
||||||
edge_conf['edge']['min_trade_number'] = 2
|
edge_conf['edge']['min_trade_number'] = 2
|
||||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
@ -360,3 +398,11 @@ def test_process_expectancy(mocker, edge_conf):
|
|||||||
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
||||||
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
||||||
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -185,3 +184,22 @@ def test_testdata_dl_options() -> None:
|
|||||||
assert args.export == 'export/folder'
|
assert args.export == 'export/folder'
|
||||||
assert args.days == 30
|
assert args.days == 30
|
||||||
assert args.exchange == 'binance'
|
assert args.exchange == 'binance'
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_int_positive() -> None:
|
||||||
|
|
||||||
|
assert Arguments.check_int_positive("3") == 3
|
||||||
|
assert Arguments.check_int_positive("1") == 1
|
||||||
|
assert Arguments.check_int_positive("100") == 100
|
||||||
|
|
||||||
|
with pytest.raises(argparse.ArgumentTypeError):
|
||||||
|
Arguments.check_int_positive("-2")
|
||||||
|
|
||||||
|
with pytest.raises(argparse.ArgumentTypeError):
|
||||||
|
Arguments.check_int_positive("0")
|
||||||
|
|
||||||
|
with pytest.raises(argparse.ArgumentTypeError):
|
||||||
|
Arguments.check_int_positive("3.5")
|
||||||
|
|
||||||
|
with pytest.raises(argparse.ArgumentTypeError):
|
||||||
|
Arguments.check_int_positive("DeadBeef")
|
||||||
|
Loading…
Reference in New Issue
Block a user