using named tuples for keeping pairs data
This commit is contained in:
parent
d7821acbf0
commit
14bfd4b7ee
@ -13,6 +13,7 @@ from freqtrade.arguments import Arguments
|
|||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
from freqtrade.strategy.resolver import IStrategy, StrategyResolver
|
from freqtrade.strategy.resolver import IStrategy, StrategyResolver
|
||||||
|
from collections import namedtuple
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -21,16 +22,11 @@ logger = logging.getLogger(__name__)
|
|||||||
class Edge():
|
class Edge():
|
||||||
|
|
||||||
config: Dict = {}
|
config: Dict = {}
|
||||||
_last_updated: int # Timestamp of pairs last updated time
|
_cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
|
||||||
_cached_pairs: list = [] # Keeps an array of
|
|
||||||
# [pair, stoploss, winrate, risk reward ratio, required risk reward, expectancy]
|
|
||||||
|
|
||||||
_total_capital: float
|
|
||||||
_allowed_risk: float
|
|
||||||
_since_number_of_days: int
|
|
||||||
_timerange: TimeRange
|
|
||||||
|
|
||||||
def __init__(self, config: Dict[str, Any], exchange=None) -> None:
|
def __init__(self, config: Dict[str, Any], exchange=None) -> None:
|
||||||
|
|
||||||
|
# Increasing recursive limit as with need it for large datasets
|
||||||
sys.setrecursionlimit(10000)
|
sys.setrecursionlimit(10000)
|
||||||
self.config = config
|
self.config = config
|
||||||
self.exchange = exchange
|
self.exchange = exchange
|
||||||
@ -42,13 +38,18 @@ class Edge():
|
|||||||
self.advise_buy = self.strategy.advise_buy
|
self.advise_buy = self.strategy.advise_buy
|
||||||
|
|
||||||
self.edge_config = self.config.get('edge', {})
|
self.edge_config = self.config.get('edge', {})
|
||||||
self._cached_pairs: list = []
|
|
||||||
self._total_capital = self.edge_config.get('total_capital_in_stake_currency')
|
|
||||||
self._allowed_risk = self.edge_config.get('allowed_risk')
|
|
||||||
self._since_number_of_days = self.edge_config.get('calculate_since_number_of_days', 14)
|
|
||||||
self._last_updated = 0
|
|
||||||
|
|
||||||
self._timerange = Arguments.parse_timerange("%s-" % arrow.now().shift(
|
# pair info data type
|
||||||
|
self._pair_info = namedtuple(
|
||||||
|
'pair_info', 'stoploss, winrate, risk_reward_ratio, required_risk_reward, expectancy')
|
||||||
|
self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
|
||||||
|
|
||||||
|
self._total_capital: float = self.edge_config.get('total_capital_in_stake_currency')
|
||||||
|
self._allowed_risk: float = self.edge_config.get('allowed_risk')
|
||||||
|
self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14)
|
||||||
|
self._last_updated: int = 0 # Timestamp of pairs last updated time
|
||||||
|
|
||||||
|
self._timerange: TimeRange = Arguments.parse_timerange("%s-" % arrow.now().shift(
|
||||||
days=-1 * self._since_number_of_days).format('YYYYMMDD'))
|
days=-1 * self._since_number_of_days).format('YYYYMMDD'))
|
||||||
|
|
||||||
self.fee = self.exchange.get_fee()
|
self.fee = self.exchange.get_fee()
|
||||||
@ -132,34 +133,24 @@ class Edge():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def stake_amount(self, pair: str) -> float:
|
def stake_amount(self, pair: str) -> float:
|
||||||
info = [x for x in self._cached_pairs if x[0] == pair][0]
|
stoploss = self._cached_pairs[pair].stoploss
|
||||||
stoploss = info[1]
|
|
||||||
allowed_capital_at_risk = round(self._total_capital * self._allowed_risk, 5)
|
allowed_capital_at_risk = round(self._total_capital * self._allowed_risk, 5)
|
||||||
position_size = abs(round((allowed_capital_at_risk / stoploss), 5))
|
position_size = abs(round((allowed_capital_at_risk / stoploss), 5))
|
||||||
return position_size
|
return position_size
|
||||||
|
|
||||||
def stoploss(self, pair: str) -> float:
|
def stoploss(self, pair: str) -> float:
|
||||||
info = [x for x in self._cached_pairs if x[0] == pair][0]
|
return self._cached_pairs[pair].stoploss
|
||||||
return info[1]
|
|
||||||
|
|
||||||
def filter(self, pairs) -> list:
|
def filter(self, pairs) -> list:
|
||||||
# Filtering pairs acccording to the expectancy
|
|
||||||
filtered_expectancy: list = []
|
|
||||||
|
|
||||||
# [pair, stoploss, winrate, risk reward ratio, required risk reward, expectancy]
|
final = []
|
||||||
filtered_expectancy = [
|
|
||||||
x[0] for x in self._cached_pairs if (
|
for pair, info in self._cached_pairs.items():
|
||||||
(x[5] > float(
|
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
|
||||||
self.edge_config.get(
|
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)) and \
|
||||||
'minimum_expectancy',
|
pair in pairs:
|
||||||
0.2))) & (
|
final.append(pair)
|
||||||
x[2] > float(
|
|
||||||
self.edge_config.get(
|
|
||||||
'minimum_winrate',
|
|
||||||
0.60))))]
|
|
||||||
|
|
||||||
# Only return pairs which are included in "pairs" argument list
|
|
||||||
final = [x for x in filtered_expectancy if x in pairs]
|
|
||||||
if final:
|
if final:
|
||||||
logger.info(
|
logger.info(
|
||||||
'Edge validated only %s',
|
'Edge validated only %s',
|
||||||
@ -220,7 +211,7 @@ class Edge():
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _process_expectancy(self, results: DataFrame) -> list:
|
def _process_expectancy(self, results: DataFrame) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs
|
This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs
|
||||||
The calulation will be done per pair and per strategy.
|
The calulation will be done per pair and per strategy.
|
||||||
@ -246,7 +237,7 @@ class Edge():
|
|||||||
#######################################################################
|
#######################################################################
|
||||||
|
|
||||||
if results.empty:
|
if results.empty:
|
||||||
return []
|
return {}
|
||||||
|
|
||||||
groupby_aggregator = {
|
groupby_aggregator = {
|
||||||
'profit_abs': [
|
'profit_abs': [
|
||||||
@ -286,12 +277,17 @@ class Edge():
|
|||||||
df = df.sort_values(by=['expectancy', 'stoploss'], ascending=False).groupby(
|
df = df.sort_values(by=['expectancy', 'stoploss'], ascending=False).groupby(
|
||||||
'pair').first().sort_values(by=['expectancy'], ascending=False).reset_index()
|
'pair').first().sort_values(by=['expectancy'], ascending=False).reset_index()
|
||||||
|
|
||||||
# dropping unecessary columns
|
final = {}
|
||||||
df.drop(columns=['nb_loss_trades', 'nb_win_trades', 'average_win', 'average_loss',
|
for x in df.itertuples():
|
||||||
'profit_sum', 'loss_sum', 'avg_trade_duration', 'nb_trades'], inplace=True)
|
final[x.pair] = self._pair_info(
|
||||||
|
x.stoploss,
|
||||||
|
x.winrate,
|
||||||
|
x.risk_reward_ratio,
|
||||||
|
x.required_risk_reward,
|
||||||
|
x.expectancy)
|
||||||
|
|
||||||
# Returning an array of pairs in order of "expectancy"
|
# Returning a list of pairs in order of "expectancy"
|
||||||
return df.values
|
return final
|
||||||
|
|
||||||
def _find_trades_for_stoploss_range(self, ticker_data, pair, stoploss_range):
|
def _find_trades_for_stoploss_range(self, ticker_data, pair, stoploss_range):
|
||||||
buy_column = ticker_data['buy'].values
|
buy_column = ticker_data['buy'].values
|
||||||
|
@ -2,6 +2,7 @@ from freqtrade.tests.conftest import get_patched_exchange
|
|||||||
from freqtrade.edge import Edge
|
from freqtrade.edge import Edge
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
|
from collections import namedtuple
|
||||||
import arrow
|
import arrow
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import math
|
import math
|
||||||
@ -20,17 +21,19 @@ from unittest.mock import MagicMock
|
|||||||
ticker_start_time = arrow.get(2018, 10, 3)
|
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}
|
||||||
|
_pair_info = namedtuple(
|
||||||
|
'pair_info', 'stoploss, winrate, risk_reward_ratio, required_risk_reward, expectancy')
|
||||||
|
|
||||||
|
|
||||||
def test_filter(mocker, default_conf):
|
def test_filter(mocker, default_conf):
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
edge = Edge(default_conf, exchange)
|
edge = Edge(default_conf, exchange)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
return_value=[
|
return_value={
|
||||||
['E/F', -0.01, 0.66, 3.71, 0.50, 1.71],
|
'E/F': _pair_info(-0.01, 0.66, 3.71, 0.50, 1.71),
|
||||||
['C/D', -0.01, 0.66, 3.71, 0.50, 1.71],
|
'C/D': _pair_info(-0.01, 0.66, 3.71, 0.50, 1.71),
|
||||||
['N/O', -0.01, 0.66, 3.71, 0.50, 1.71]
|
'N/O': _pair_info(-0.01, 0.66, 3.71, 0.50, 1.71)
|
||||||
]
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
pairs = ['A/B', 'C/D', 'E/F', 'G/H']
|
pairs = ['A/B', 'C/D', 'E/F', 'G/H']
|
||||||
@ -41,11 +44,11 @@ def test_stoploss(mocker, default_conf):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
edge = Edge(default_conf, exchange)
|
edge = Edge(default_conf, exchange)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
return_value=[
|
return_value={
|
||||||
['E/F', -0.01, 0.66, 3.71, 0.50, 1.71],
|
'E/F': _pair_info(-0.01, 0.66, 3.71, 0.50, 1.71),
|
||||||
['C/D', -0.01, 0.66, 3.71, 0.50, 1.71],
|
'C/D': _pair_info(-0.01, 0.66, 3.71, 0.50, 1.71),
|
||||||
['N/O', -0.01, 0.66, 3.71, 0.50, 1.71]
|
'N/O': _pair_info(-0.01, 0.66, 3.71, 0.50, 1.71)
|
||||||
]
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
assert edge.stoploss('E/F') == -0.01
|
assert edge.stoploss('E/F') == -0.01
|
||||||
@ -61,7 +64,7 @@ def _validate_ohlc(buy_ohlc_sell_matrice):
|
|||||||
|
|
||||||
def _build_dataframe(buy_ohlc_sell_matrice):
|
def _build_dataframe(buy_ohlc_sell_matrice):
|
||||||
_validate_ohlc(buy_ohlc_sell_matrice)
|
_validate_ohlc(buy_ohlc_sell_matrice)
|
||||||
tickers = []
|
tickers= []
|
||||||
for ohlc in buy_ohlc_sell_matrice:
|
for ohlc in buy_ohlc_sell_matrice:
|
||||||
ticker = {
|
ticker = {
|
||||||
'date': ticker_start_time.shift(
|
'date': ticker_start_time.shift(
|
||||||
@ -79,9 +82,9 @@ def _build_dataframe(buy_ohlc_sell_matrice):
|
|||||||
|
|
||||||
frame = DataFrame(tickers)
|
frame = DataFrame(tickers)
|
||||||
frame['date'] = to_datetime(frame['date'],
|
frame['date'] = to_datetime(frame['date'],
|
||||||
unit='ms',
|
unit = 'ms',
|
||||||
utc=True,
|
utc = True,
|
||||||
infer_datetime_format=True)
|
infer_datetime_format = True)
|
||||||
|
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
@ -92,17 +95,17 @@ def _time_on_candle(number):
|
|||||||
|
|
||||||
|
|
||||||
def test_edge_heartbeat_calculate(mocker, default_conf):
|
def test_edge_heartbeat_calculate(mocker, default_conf):
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange=get_patched_exchange(mocker, default_conf)
|
||||||
edge = Edge(default_conf, exchange)
|
edge=Edge(default_conf, exchange)
|
||||||
heartbeat = default_conf['edge']['process_throttle_secs']
|
heartbeat=default_conf['edge']['process_throttle_secs']
|
||||||
|
|
||||||
# should not recalculate if heartbeat not reached
|
# should not recalculate if heartbeat not reached
|
||||||
edge._last_updated = arrow.utcnow().timestamp - heartbeat + 1
|
edge._last_updated=arrow.utcnow().timestamp - heartbeat + 1
|
||||||
|
|
||||||
assert edge.calculate() is False
|
assert edge.calculate() is False
|
||||||
|
|
||||||
|
|
||||||
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
|
def mocked_load_data(datadir, pairs = [], ticker_interval = '0m', refresh_pairs = False,
|
||||||
timerange=None, exchange=None):
|
timerange=None, exchange=None):
|
||||||
hz = 0.1
|
hz = 0.1
|
||||||
base = 0.001
|
base = 0.001
|
||||||
@ -202,13 +205,12 @@ def test_process_expectancy(mocker, default_conf):
|
|||||||
final = edge._process_expectancy(trades_df)
|
final = edge._process_expectancy(trades_df)
|
||||||
assert len(final) == 1
|
assert len(final) == 1
|
||||||
|
|
||||||
assert final[0][0] == 'TEST/BTC'
|
assert 'TEST/BTC' in final
|
||||||
assert final[0][1] == -0.9
|
assert final['TEST/BTC'].stoploss == -0.9
|
||||||
assert round(final[0][2], 10) == 0.3333333333
|
assert round(final['TEST/BTC'].winrate, 10) == 0.3333333333
|
||||||
assert round(final[0][3], 10) == 306.5384615384
|
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
||||||
assert round(final[0][4], 10) == 2.0
|
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
||||||
assert round(final[0][5], 10) == 101.5128205128
|
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
||||||
|
|
||||||
|
|
||||||
# 1) Open trade should be removed from the end
|
# 1) Open trade should be removed from the end
|
||||||
def test_case_1(mocker, default_conf):
|
def test_case_1(mocker, default_conf):
|
||||||
|
Loading…
Reference in New Issue
Block a user