using named tuples for keeping pairs data

This commit is contained in:
misagh 2018-11-04 18:11:58 +01:00
parent d7821acbf0
commit 14bfd4b7ee
2 changed files with 64 additions and 66 deletions

View File

@ -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

View File

@ -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):