Блог им. Riskplayer
Best params (4, 8)Т.е. система следующая: если SMA(4) пересекает снизу вверх SMA(8), то покупаем, если пересекает сверху вниз, то ликвидируем позицию.
SBER average profit = 1.654% pf = 2.3 Max win = 31.144% max loss -18.70% GAZP average profit = 0.883% pf = 1.7 Max win = 37.161% max loss -7.26% GMKN average profit = 0.728% pf = 1.6 Max win = 19.071% max loss -14.99% LKOH average profit = 0.365% pf = 1.2 Max win = 19.565% max loss -9.32% ROSN average profit = 0.920% pf = 1.8 Max win = 20.785% max loss -13.21% MOEX average profit = 0.989% pf = 1.8 Max win = 29.939% max loss -8.98% NVTK average profit = 0.591% pf = 1.4 Max win = 25.483% max loss -11.18% PLZL average profit = 2.084% pf = 2.4 Max win = 64.463% max loss -20.14% AFLT average profit = 1.125% pf = 1.7 Max win = 37.095% max loss -9.32% MTLR average profit = 3.068% pf = 2.8 Max win = 160.692% max loss -25.22% NLMK average profit = 1.057% pf = 1.7 Max win = 34.744% max loss -13.75% MAGN average profit = 1.342% pf = 1.9 Max win = 33.568% max loss -16.13% TRNFP average profit = 0.955% pf = 1.7 Max win = 33.902% max loss -11.36% MGNT average profit = 0.318% pf = 1.2 Max win = 21.875% max loss -18.84%График накопленной доходности без реинвестирования (включая испытательный и контрольный диапазон):


import pandas as pd
import numpy as np
from datetime import datetime
import itertools
import talib
def get_daily_data(ticker):
filename = "..\\moex.h5"
with pd.HDFStore(filename) as store:
data_1min = store[ticker]
data_1min = data_1min[['Close']]
#df = data_1min.resample('D').agg({'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last'})
df = data_1min.resample('D').agg({'Close': 'last'})
df = df.dropna()
return df
def get_arr_of_trades(df, first_timeperiod = 10, second_timeperiod = 30):
df['fast_sma'] = talib.SMA(df['Close'], first_timeperiod)
df['slow_sma'] = talib.SMA(df['Close'], second_timeperiod)
df['signal_buy'] = (df['fast_sma'] > df['slow_sma']) & (df['fast_sma'].shift() < df['slow_sma'].shift())
df['signal_sell'] = (df['fast_sma'] < df['slow_sma']) & (df['fast_sma'].shift() > df['slow_sma'].shift())
df['pos'] = np.NaN
df['pos'] = df['pos'].mask(df['signal_buy'], 1)
df['pos'] = df['pos'].mask(df['signal_sell'], 0)
df.loc[df.index[0], 'pos'] = 0
df['pos'] = df['pos'].ffill()
df['diff'] = df['Close'].diff()
df['denominator'] = np.NaN
df['denominator'] = df['denominator'].mask((df['pos']==1) & (df['pos'].shift() == 0), df['Close'])
df['denominator'] = df['denominator'].ffill()
df['ret'] = 0
df['ret'] = df['diff']*df['pos'].shift()/df['denominator']
df['cumret'] = df['ret'].cumsum()
df['isStartNewTrade'] = 0
df['isStartNewTrade'] = df['isStartNewTrade'].mask((df['pos']==1) & (df['pos'].shift() == 0), 1)
df['count'] = df['isStartNewTrade'].cumsum()
df['count'] = df['count'].mask(df['pos']== 0, 0)
df['count'] = df['count'].shift()
df['isFinishTrade'] = False
df['isFinishTrade'] = df['isFinishTrade'].mask((df['pos'] == 0) & (df['pos'].shift() == 1), True)
df['isFinishTrade'] = df['isFinishTrade'].mask((df['pos'] == 1) & (df.index == df.index[-1]) & (df['pos'].shift() == 1), True)
return df
list_tickers = ['sber', 'gazp', 'gmkn', 'lkoh', 'rosn', 'moex', 'nvtk', 'plzl', 'aflt',
'mtlr', 'nlmk', 'magn', 'trnfp', 'mgnt']
list_tickers = [x.upper() for x in list_tickers]
start_date = datetime(2015, 1, 1)
finish_date = datetime(2023, 1, 1)
profil = np.zeros((len(list_tickers), 51, 51))
for idx, ticker in enumerate(list_tickers):
print(ticker)
df = get_daily_data(ticker)
df = df[(df.index > start_date) & (df.index < finish_date)]
max_period = profil.shape[1]
iterator = itertools.combinations(range(2, max_period), 2)
for item in iterator:
i = item[0]
j = item[1]
df = get_arr_of_trades(df, i, j)
profil[idx, i, j] = df.iloc[-1]['cumret']
sum_profil = profil.mean(axis = 0)
result = np.unravel_index(np.argmax(sum_profil), sum_profil.shape)
first_timeperiod = result[0]
second_timeperiod = result[1]
print('Best params', result)
rng = pd.date_range(start = start_date.date(), end= datetime.today().date())
rng = rng[rng.weekday < 5]
# df_simple для учета простых процентов
# df_compound для учета сложного процента
df_simple = pd.DataFrame(index = rng)
df_compound = pd.DataFrame(index = rng)
for idx, ticker in enumerate(list_tickers):
df = get_daily_data(ticker)
df = df[df.index > start_date]
df = get_arr_of_trades(df, first_timeperiod, second_timeperiod)
df_simple[ticker] = df['cumret']
trades = df.groupby(['count'])['ret'].sum()
trades = trades[trades.index > 0]
df_t = pd.DataFrame(data = trades.values, index = df[df['isFinishTrade']].index, columns = [ticker])
df_compound = df_compound.join(df_t)
av_profit = trades.mean()
pf = - trades[trades > 0].sum()/trades[trades < 0].sum()
max_win = trades.max()
max_loss = trades.min()
print(f'{ticker} average profit = {av_profit:.3%} pf = {pf:.2} Max win = {max_win:.3%} max loss {max_loss:.2%}')
df_simple.iloc[0] = df_simple.iloc[0].fillna(0)
df_simple = df_simple.ffill()
df_simple['average_simple_ret'] = df_simple[list_tickers].mean(axis = 1)
#df_simple['average_simple_ret'].plot(grid = True, title = 'Simple percent')
df_compound = df_compound.fillna(0) + 1
df_compound = df_compound.cumprod() - 1
df_compound['average_compound_ret'] = df_compound[list_tickers].mean(axis = 1)
#df_compound['average_compound_ret'].plot(grid = True, title = 'Compound percent')
pd.concat([df_simple['average_simple_ret'], df_compound['average_compound_ret']], axis=1).plot(grid = True)
Работает чуть лучше, чем обычные SMA.