In [4]:
NB_VERSION = 1,0
import sys
import datetime
import numpy as np
import pandas as pd
print('Verze notebooku:', '.'.join(map(str, NB_VERSION)))
print('Verze pythonu:', '.'.join(map(str, sys.version_info[0:3])))
print('---')
import pandas_datareader as pdr
import pandas_datareader.data as pdr_web
from matplotlib import __version__ as matplotlib_version
print('NumPy:', np.__version__)
print('Pandas:', pd.__version__)
print('pandas-datareader:', pdr.__version__)
print('Matplotlib:', matplotlib_version)
Data handler
, který funguje jako prostředík, který se stará o dataStrategy
- strategie, která generuje nákupní signály dle jednotlivých dat z data handleru
Portfolio
, které generuje příkazy a spravuje zisk nebo ztrátu (známé jako PnL, od Profit & Loss)Execution handler
, který odesílá příkazy brokerovi a zpracovává odpovědi, které získá jako odpověď (tzv. fills)Prvními dvěma body jsem se zabýval v minulých příspěvcích. Teď je na řadě Portfolio
. Tento článek se bude zabývat jak si sestavím jednoduché portfolio a zároveň ho pustím na historických datech a získám tzv. backtest.
Backtest je testování strategie na relevatních historických datech. Backtest mi může ve velmi krátkém čase poskytnout informaci, zda je vhodné se dál zaměřit na danou myšlenku obchodní strategie a více ji rozvíjet, a nebo se tímto stylem dál vůbec zabývat.
Získám data akciového indexu společnosti Apple a vytvořím obchodní signály. Celý kód jsem převzal z minulého příspěvku Tvorby obchodní strategie.
In [5]:
start_date = datetime.datetime(2008, 1, 1)
end_date = datetime.datetime.now()
short_period = 30
long_period = 90
ohlc_data = pdr_web.DataReader("AAPL", 'google', start=start_date, end=end_date)
signals = pd.DataFrame(index=ohlc_data.index)
signals['signal'] = 0.0
signals['short_sma'] = ohlc_data['Close'].rolling(window=short_period, min_periods=1, center=False).mean()
signals['long_sma'] = ohlc_data['Close'].rolling(window=long_period, min_periods=1, center=False).mean()
signals['signal'][short_period:] = np.where(signals['short_sma'][short_period:]
> signals['long_sma'][short_period:], 1.0, 0.0)
signals['positions'] = signals['signal'].diff()
signals.iloc[73:78] # pozice +1.0
signals.iloc[143:147] # pozice -1.0
Out[5]:
In [6]:
# Počáteční kapitál
initial_capital= float(100000.0)
print(f'{initial_capital} $')
# Příprava DataFramu
positions = pd.DataFrame(index=signals.index).fillna(0.0)
positions.head(3)
Out[6]:
In [7]:
# definovaný objem
lots = 100
# Nákup o objemu 100
positions['AAPL'] = lots * signals['signal']
Pro názornost - nákup:
100 akcií nakoupím 18.4.2008 a budu je dále držet.
In [8]:
# vizualizace nákupu
positions.iloc[72:76] # signál = +1.0
Out[8]:
Pro názornost - prodej:
Následně 30.7.2008 svoji pozici odprodám a nebudu držet žádné akcie.
In [9]:
# vizualizace zpětného prodeje
positions.iloc[143:147] # signál = 0.0
Out[9]:
In [10]:
# Výpočet vývoje hodnoty nakoupených akcií
portfolio = positions.multiply(ohlc_data['Close'], axis=0)
portfolio.iloc[72:80]
Out[10]:
In [11]:
# Získám velikost pozice pro nákupní/prodejní signál pro určitý den
pos_diff = positions.diff()
pos_diff.iloc[143:147]
#pos_diff.iloc[72:76]
Out[11]:
In [12]:
# Výpočet celkové aktuální hodnoty obchodovaného portfolia
portfolio['holdings'] = (positions.multiply(ohlc_data['Close'], axis=0)).sum(axis=1)
portfolio.iloc[72:76]
Out[12]:
In [13]:
# Výpočet hodnoty zbývajícího kapitálu
portfolio['cash'] = initial_capital - (pos_diff.multiply(ohlc_data['Close'], axis=0)).sum(axis=1).cumsum()
portfolio.iloc[143:147]
Out[13]:
In [14]:
# Celková hodnota kapitálu
portfolio['total'] = portfolio['cash'] + portfolio['holdings']
portfolio.iloc[143:147]
Out[14]:
In [15]:
# Návratnost -> procentní změna kapitálu pro každý den
portfolio['returns'] = portfolio['total'].pct_change()
portfolio.iloc[143:147]
Out[15]:
In [16]:
initial_capital= float(100000.0)
positions = pd.DataFrame(index=signals.index).fillna(0.0)
positions['AAPL'] = 100 * signals['signal']
portfolio = positions.multiply(ohlc_data['Close'], axis=0)
pos_diff = positions.diff()
portfolio['holdings'] = (positions.multiply(ohlc_data['Close'], axis=0)).sum(axis=1)
portfolio['cash'] = initial_capital - (pos_diff.multiply(ohlc_data['Close'], axis=0)).sum(axis=1).cumsum()
portfolio['total'] = portfolio['cash'] + portfolio['holdings']
portfolio['returns'] = portfolio['total'].pct_change()
portfolio.tail()
Out[16]:
In [21]:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(15,7))
ax1 = fig.add_subplot(111, ylabel='Hodnota portfolia v $')
# graf průběhu equity v USD
portfolio['total'].plot(ax=ax1, lw=2.)
# vložení vstupů do pozic
ax1.plot(portfolio.loc[signals.positions == 1.0].index,
portfolio.total[signals.positions == 1.0],
'^', markersize=10, color='m')
ax1.plot(portfolio.loc[signals.positions == -1.0].index,
portfolio.total[signals.positions == -1.0],
'v', markersize=10, color='k')
# zobrazení připraveného grafu
plt.show()
Graf vývoje hodnoty portfolia při obchodování jednoduché strategie na bázi klouzavých průměrů.
Připravil jsem strategii, která (pouze) nakupuje akcie společnosti Apple. Backtest ukázal, že v případě obchodování této strategie od roku 2008 cca do poloviny roku 2017, zhodnotil bych svůj kapitál. Tohle je ale samozřejmě výukový příklad a obchodovat jen na základě tohoto backtestu by nemuselo dopadnout dobře. Zkuste si zkopírovat kód a nastavit datum obchodování jen na rok 2015 (od 1.1.2015 do 1.1.2016). Ale to je už jiná kapitola.
Je vidět, že sestavení své vlastní strategie (14 řádků kódu) a následný backtest (10 řádků kódu) nemusí být vůbec složitý. Následně si jednoduše můžu zobrazit výsledek a ihned na první pohled vidět, zda má vůbec smysl se podobnou strategií dál zabývat.