Hybrid: RandomForest direction scorer + Winner exit rules — 8 coins per-pair
Timeframe
15m
Direction
Long & Short
Stoploss
-2.5%
Trailing Stop
Yes
ROI
0m: 6.0%, 480m: 4.0%, 1440m: 2.5%, 4320m: 0.0%
Interface Version
3
Startup Candles
3000
Indicators
8
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
from pandas import DataFrame
import talib.abstract as ta, numpy as np
from freqtrade.strategy import IStrategy
def f(d):
df=d.copy()
for p in[1,3,6,12,24]:df['r'+str(p)]=df['close'].pct_change(p)
df['hlr']=(df['high']-df['low'])/df['close'];df['cp']=(df['close']-df['low'])/(df['high']-df['low']+1e-6)
for p in[9,21,55]:e=ta.EMA(df,p);df['e'+str(p)+'d']=(df['close']-e)/e*100
md=ta.MACD(df);df['md']=md['macd'];df['ms']=md['macdsignal'];df['mh']=md['macdhist']
df['rsi']=ta.RSI(df,14);df['adx']=ta.ADX(df,14)
bb=ta.BBANDS(df,20);df['bw']=(bb['upperband']-bb['lowerband'])/bb['middleband'];df['bbp']=(df['close']-bb['lowerband'])/(bb['upperband']-bb['lowerband']+1e-6)
df['atr']=ta.ATR(df,14)/df['close']*100;df['vr']=df['volume']/ta.SMA(df['volume'],20)
st=ta.STOCH(df);df['sk']=st['slowk'];df['sd']=st['slowd']
return df
C=['r1','r3','r6','r12','r24','hlr','cp','e9d','e21d','e55d','md','ms','mh','rsi','adx','bw','bbp','atr','vr','sk','sd']
class HR(IStrategy):
"""Hybrid: RandomForest direction scorer + Winner exit rules — 8 coins per-pair"""
INTERFACE_VERSION=3;timeframe='15m';can_short=True
stoploss=-0.025;trailing_stop=True;trailing_stop_positive=0.005;trailing_stop_positive_offset=0.018
trailing_only_offset_is_reached=True;minimal_roi={"0":0.06,"480":0.04,"1440":0.025,"4320":0}
max_open_trades=4;startup_candle_count=3000;process_only_new_candles=False;use_exit_signal=False
def __init__(self,c):super().__init__(c);self.m={};self.sm={};self.ss={}
def populate_indicators(self,d,meta):
p=meta['pair'];n=len(d)
if p not in self.m and n>=2200:self._t(d.iloc[:2000].copy(),p)
if p in self.m:
d2=f(d);X=np.nan_to_num(d2[C].values,0,0,0);Xs=(X-self.sm[p])/self.ss[p]
pp=self.m[p].predict_proba(Xs);cl=self.m[p].classes_
li=list(cl).index(2)if 2 in cl else 0;si=list(cl).index(0)if 0 in cl else 0
d['xs']=pp[:,li]-pp[:,si];d['xc']=np.maximum(pp[:,li],pp[:,si])
return d
def _t(self,df,p):
from sklearn.ensemble import RandomForestClassifier
d2=f(df);X=np.nan_to_num(d2[C].values,0,0,0);y=np.where(d2['close'].pct_change(12).shift(-12).fillna(0)>0.005,2,np.where(d2['close'].pct_change(12).shift(-12).fillna(0)<-0.005,0,1))
v=np.arange(len(X)-12);v2=np.array([i for i in v if not np.isnan(y[i]) and not np.isnan(X[i]).any()])
if len(v2)<400:return False
Xt,yt=X[v2],y[v2];self.sm[p]=Xt.mean(0);self.ss[p]=Xt.std(0)+1e-6
self.m[p]=RandomForestClassifier(n_estimators=200,max_depth=10,min_samples_leaf=15,class_weight='balanced',random_state=42,n_jobs=-1)
self.m[p].fit((Xt-self.sm[p])/self.ss[p],yt);return True
def populate_entry_trend(self,d,m):
if'xs'not in d.columns:return d
d.loc[(d['xc']>0.65)&(d['xs']>0),['enter_long','enter_tag']]=(1,'L')
d.loc[(d['xc']>0.65)&(d['xs']<0),['enter_short','enter_tag']]=(1,'S')
return d
def populate_exit_trend(self,d,m):return d
class HX(IStrategy):
"""Hybrid: XGBoost direction scorer + Winner exit rules — 8 coins per-pair 5-model ensemble"""
INTERFACE_VERSION=3;timeframe='15m';can_short=True
stoploss=-0.025;trailing_stop=True;trailing_stop_positive=0.005;trailing_stop_positive_offset=0.018
trailing_only_offset_is_reached=True;minimal_roi={"0":0.06,"480":0.04,"1440":0.025,"4320":0}
max_open_trades=4;startup_candle_count=3000;process_only_new_candles=False;use_exit_signal=False
def __init__(self,c):super().__init__(c);self.m={};self.sm={};self.ss={}
def populate_indicators(self,d,meta):
p=meta['pair'];n=len(d)
if p not in self.m and n>=2200:self._t(d.iloc[:2000].copy(),p)
if p in self.m:
d2=f(d);X=np.nan_to_num(d2[C].values,0,0,0);Xs=(X-self.sm[p])/self.ss[p]
ap=np.mean([m.predict_proba(Xs)for m in self.m[p]],0);cl=self.m[p][0].classes_
li=list(cl).index(2)if 2 in cl else 0;si=list(cl).index(0)if 0 in cl else 0
d['xs']=ap[:,li]-ap[:,si];d['xc']=np.maximum(ap[:,li],ap[:,si])
return d
def _t(self,df,p):
import xgboost as xgb
d2=f(df);X=np.nan_to_num(d2[C].values,0,0,0);y=np.where(d2['close'].pct_change(12).shift(-12).fillna(0)>0.005,2,np.where(d2['close'].pct_change(12).shift(-12).fillna(0)<-0.005,0,1))
v=np.arange(len(X)-12);v2=np.array([i for i in v if not np.isnan(y[i]) and not np.isnan(X[i]).any()])
if len(v2)<400:return False
Xt,yt=X[v2],y[v2];self.sm[p]=Xt.mean(0);self.ss[p]=Xt.std(0)+1e-6;Xs=(Xt-self.sm[p])/self.ss[p]
u,cnt=np.unique(yt,return_counts=True);w={u[i]:len(yt)/(len(u)*cnt[i])for i in range(len(u))};sw=np.array([w[z]for z in yt])
self.m[p]=[]
for s in[42,73,99,17,55]:m=xgb.XGBClassifier(n_estimators=200,max_depth=4,learning_rate=0.04,subsample=0.8,colsample_bytree=0.7,min_child_weight=3,reg_alpha=0.2,reg_lambda=1.0,random_state=s,verbosity=0);m.fit(Xs,yt,sample_weight=sw);self.m[p].append(m)
return True
def populate_entry_trend(self,d,m):
if'xs'not in d.columns:return d
d.loc[(d['xc']>0.7)&(d['xs']>0),['enter_long','enter_tag']]=(1,'L')
d.loc[(d['xc']>0.7)&(d['xs']<0),['enter_short','enter_tag']]=(1,'S')
return d
def populate_exit_trend(self,d,m):return d