Зрелые библиотеки Python, такие как matplotlib, pandas и scikit-learn, также уменьшают необходимость написания кода или создания собственных реализаций известных алгоритмов.
Сама стратегия прогнозирования основана на технике машинного обучения, известной как анализатор квадратического дискриминанта, который тесно связан с линейным анализатором дискриминанта.
Прогнозщик использует предыдущие две ежедневные доходности в качестве набора факторов для прогнозирования сегодняшнего направления фондового рынка. Если вероятность того, что день будет
Обратите внимание, что это не особенно реалистичная стратегия торговли! Вряд ли мы когда-либо достигнем цены открытия или закрытия из-за многих факторов, таких как чрезмерная волатильность открытия, маршрутизация ордеров брокером и потенциальные проблемы с ликвидностью вокруг открытия / закрытия. Кроме того, мы не включили затраты на транзакции. Это, вероятно, будет значительный процент от доходности, поскольку ежедневно проводится оборотная торговля. Таким образом, наш прогнозчик должен быть относительно точным в прогнозировании ежедневной доходности, иначе затраты на транзакции съедят все наши торговые доходы.
Как и в других учебниках, связанных с Python/panda, я использовал следующие библиотеки:
Использование snp_forecast.py требуетbacktest.pyиз этого предыдущего учебника.forecast.pyПервый шаг - импортировать необходимые модули и объекты:
# snp_forecast.py
import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sklearn
from pandas.io.data import DataReader
from sklearn.qda import QDA
from backtest import Strategy, Portfolio
from forecast import create_lagged_series
После того, как все соответствующие библиотеки и модули включены, пришло время подклассифицировать абстрактный базовый класс Стратегии, как мы уже делали в предыдущих учебниках. SNPForecastingStrategy предназначен для подключения к индексу акций S&P500 Анализатора квадратных дискриминаторов в качестве средства прогнозирования его будущей стоимости. Подключение модели осуществляется в методе fit_model ниже, в то время как фактические сигналы генерируются из метода generate_signals. Это соответствует интерфейсу класса Стратегии.
Подробности о том, как работает анализатор квадратных дискриминантов, а также реализация Python ниже, подробно описаны в предыдущей статье о прогнозировании финансовых временных рядов.
# snp_forecast.py
class SNPForecastingStrategy(Strategy):
"""
Requires:
symbol - A stock symbol on which to form a strategy on.
bars - A DataFrame of bars for the above symbol."""
def __init__(self, symbol, bars):
self.symbol = symbol
self.bars = bars
self.create_periods()
self.fit_model()
def create_periods(self):
"""Create training/test periods."""
self.start_train = datetime.datetime(2001,1,10)
self.start_test = datetime.datetime(2005,1,1)
self.end_period = datetime.datetime(2005,12,31)
def fit_model(self):
"""Fits a Quadratic Discriminant Analyser to the
US stock market index (^GPSC in Yahoo)."""
# Create a lagged series of the S&P500 US stock market index
snpret = create_lagged_series(self.symbol, self.start_train,
self.end_period, lags=5)
# Use the prior two days of returns as
# predictor values, with direction as the response
X = snpret[["Lag1","Lag2"]]
y = snpret["Direction"]
# Create training and test sets
X_train = X[X.index < self.start_test]
y_train = y[y.index < self.start_test]
# Create the predicting factors for use
# in direction forecasting
self.predictors = X[X.index >= self.start_test]
# Create the Quadratic Discriminant Analysis model
# and the forecasting strategy
self.model = QDA()
self.model.fit(X_train, y_train)
def generate_signals(self):
"""Returns the DataFrame of symbols containing the signals
to go long, short or hold (1, -1 or 0)."""
signals = pd.DataFrame(index=self.bars.index)
signals['signal'] = 0.0
# Predict the subsequent period with the QDA model
signals['signal'] = self.model.predict(self.predictors)
# Remove the first five signal entries to eliminate
# NaN issues with the signals DataFrame
signals['signal'][0:5] = 0.0
signals['positions'] = signals['signal'].diff()
return signals
Теперь, когда движок прогнозирования произвел сигналы, мы можем создать MarketIntradayPortfolio. Этот объект портфеля отличается от примера, приведенного в статье Backtest пересечения скользящих средних, поскольку он осуществляет торговлю на внутридневных началах.
Портфель предназначен для покупки 500 акций SPY по цене открытия, если сигнал указывает, что произойдет подъемный день, а затем продать при закрытии.
Для достижения этой цели разница в цене между открытыми и закрытыми ценами на рынке определяется каждый день, что приводит к расчету ежедневной прибыли на 500 купленных или проданных акций. Это затем естественно приводит к кривой собственности путем суммирования прибыли/убытка за каждый день.
Вот список для MarketIntradayPortfolio:
# snp_forecast.py
class MarketIntradayPortfolio(Portfolio):
"""Buys or sells 500 shares of an asset at the opening price of
every bar, depending upon the direction of the forecast, closing
out the trade at the close of the bar.
Requires:
symbol - A stock symbol which forms the basis of the portfolio.
bars - A DataFrame of bars for a symbol set.
signals - A pandas DataFrame of signals (1, 0, -1) for each symbol.
initial_capital - The amount in cash at the start of the portfolio."""
def __init__(self, symbol, bars, signals, initial_capital=100000.0):
self.symbol = symbol
self.bars = bars
self.signals = signals
self.initial_capital = float(initial_capital)
self.positions = self.generate_positions()
def generate_positions(self):
"""Generate the positions DataFrame, based on the signals
provided by the 'signals' DataFrame."""
positions = pd.DataFrame(index=self.signals.index).fillna(0.0)
# Long or short 500 shares of SPY based on
# directional signal every day
positions[self.symbol] = 500*self.signals['signal']
return positions
def backtest_portfolio(self):
"""Backtest the portfolio and return a DataFrame containing
the equity curve and the percentage returns."""
# Set the portfolio object to have the same time period
# as the positions DataFrame
portfolio = pd.DataFrame(index=self.positions.index)
pos_diff = self.positions.diff()
# Work out the intraday profit of the difference
# in open and closing prices and then determine
# the daily profit by longing if an up day is predicted
# and shorting if a down day is predicted
portfolio['price_diff'] = self.bars['Close']-self.bars['Open']
portfolio['price_diff'][0:5] = 0.0
portfolio['profit'] = self.positions[self.symbol] * portfolio['price_diff']
# Generate the equity curve and percentage returns
portfolio['total'] = self.initial_capital + portfolio['profit'].cumsum()
portfolio['returns'] = portfolio['total'].pct_change()
return portfolio
Последним шагом является объединение целей Стратегии и Портфеля вместе сглавныйФункция получает данные для инструмента SPY, а затем создает стратегию генерации сигнала на самом индексе S&P500. Это обеспечивается тикером ^GSPC. Затем генерируется портфель MarketIntraday с начальным капиталом в 100 000 долларов США (как и в предыдущих учебниках). Наконец, рассчитываются доходы и графизируется кривая капитала.
Обратите внимание, насколько мало кода требуется на этом этапе, потому что все тяжелые вычисления выполняются в подклассах Стратегия и Портфель.
if __name__ == "__main__":
start_test = datetime.datetime(2005,1,1)
end_period = datetime.datetime(2005,12,31)
# Obtain the bars for SPY ETF which tracks the S&P500 index
bars = DataReader("SPY", "yahoo", start_test, end_period)
# Create the S&P500 forecasting strategy
snpf = SNPForecastingStrategy("^GSPC", bars)
signals = snpf.generate_signals()
# Create the portfolio based on the forecaster
portfolio = MarketIntradayPortfolio("SPY", bars, signals,
initial_capital=100000.0)
returns = portfolio.backtest_portfolio()
# Plot results
fig = plt.figure()
fig.patch.set_facecolor('white')
# Plot the price of the SPY ETF
ax1 = fig.add_subplot(211, ylabel='SPY ETF price in $')
bars['Close'].plot(ax=ax1, color='r', lw=2.)
# Plot the equity curve
ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
returns['total'].plot(ax=ax2, lw=2.)
fig.show()
В этом периоде фондовый рынок вернул 4% (при условии полной инвестиционной стратегии покупки и удержания), в то время как сам алгоритм также вернул 4%. Обратите внимание, что затраты на транзакции (такие как комиссии) не добавлены к этой системе бэкстестинга.
Прогнозные результаты стратегии S&P500 с 1 января 2005 года по 31 декабря 2006 года
В последующих статьях мы добавим реалистичные затраты на транзакции, воспользуемся дополнительными системами прогнозирования, определим показатели эффективности и предоставим инструменты оптимизации портфеля.