बैकटेस्टिंग ऐतिहासिक डेटा पर एक ट्रेडिंग रणनीति के विचार को लागू करने की शोध प्रक्रिया है ताकि पिछले प्रदर्शन का पता लगाया जा सके। विशेष रूप से, एक बैकटेस्टर रणनीति के भविष्य के प्रदर्शन के बारे में कोई गारंटी नहीं देता है। हालांकि वे रणनीति पाइपलाइन अनुसंधान प्रक्रिया का एक आवश्यक घटक हैं, जिससे उत्पादन में डालने से पहले रणनीतियों को फ़िल्टर किया जा सकता है।
इस लेख में (और जो इसके बाद आएंगे) पायथन में लिखी गई एक बुनियादी ऑब्जेक्ट-ओरिएंटेड बैकटेस्टिंग प्रणाली की रूपरेखा तैयार की जाएगी। यह प्रारंभिक प्रणाली मुख्य रूप से एक
एक मजबूत बैकटेस्टिंग सिस्टम डिजाइन करने की प्रक्रिया बेहद मुश्किल है। एक एल्गोरिथम ट्रेडिंग सिस्टम के प्रदर्शन को प्रभावित करने वाले सभी घटकों का प्रभावी ढंग से अनुकरण करना चुनौतीपूर्ण है। खराब डेटा दानेदारी, एक ब्रोकर पर ऑर्डर रूटिंग की अस्पष्टता, ऑर्डर विलंबता और कई अन्य कारक एक रणनीति के
एक बैकटेस्टिंग प्रणाली विकसित करते समय इसे लगातार
इन चिंताओं को ध्यान में रखते हुए, यहां प्रस्तुत बैकटेस्टर कुछ हद तक सरल होगा। जैसा कि हम आगे के मुद्दों (पोर्टफोलियो अनुकूलन, जोखिम प्रबंधन, लेनदेन लागत प्रबंधन) का पता लगाते हैं, बैकटेस्टर अधिक मजबूत हो जाएगा।
सामान्य तौर पर दो प्रकार के बैकटेस्टिंग सिस्टम हैं जो रुचि रखते हैं। पहला अनुसंधान-आधारित है, जिसका उपयोग मुख्य रूप से प्रारंभिक चरणों में किया जाता है, जहां अधिक गंभीर मूल्यांकन के लिए उन लोगों का चयन करने के लिए कई रणनीतियों का परीक्षण किया जाएगा। ये अनुसंधान बैकटेस्टिंग सिस्टम अक्सर पायथन, आर या मैटलैब में लिखे जाते हैं क्योंकि इस चरण में विकास की गति निष्पादन की गति से अधिक महत्वपूर्ण है।
दूसरा प्रकार का बैकटेस्टिंग सिस्टम इवेंट-आधारित होता है। यानी, यह ट्रेडिंग निष्पादन प्रणाली के समान (यदि समान नहीं है) निष्पादन लूप में बैकटेस्टिंग प्रक्रिया को निष्पादित करता है। यह एक रणनीति का अधिक कठोर मूल्यांकन प्रदान करने के लिए वास्तविक रूप से बाजार डेटा और ऑर्डर निष्पादन प्रक्रिया का मॉडल करेगा।
उत्तरार्द्ध प्रणाली अक्सर C ++ या जावा जैसी उच्च प्रदर्शन वाली भाषा में लिखी जाती है, जहां निष्पादन की गति आवश्यक है। कम आवृत्ति रणनीतियों के लिए (हालांकि अभी भी इंट्राडे), इस संदर्भ में उपयोग करने के लिए पायथन पर्याप्त से अधिक है।
अब एक ऑब्जेक्ट उन्मुख अनुसंधान आधारित बैकटेस्टिंग वातावरण के डिजाइन और कार्यान्वयन पर चर्चा की जाएगी। निम्नलिखित कारणों से ऑब्जेक्ट उन्मुखता को सॉफ्टवेयर डिजाइन प्रतिमान के रूप में चुना गया हैः
इस चरण में बैकटेस्टर को वास्तविक बाजार सटीकता की कीमत पर कार्यान्वयन की आसानी और लचीलेपन की एक उचित डिग्री के लिए डिज़ाइन किया गया है। विशेष रूप से, यह बैकटेस्टर केवल एक ही उपकरण पर कार्य करने वाली रणनीतियों को संभाल सकता है। बाद में बैकटेस्टर को उपकरणों के सेट को संभालने के लिए संशोधित किया जाएगा। प्रारंभिक बैकटेस्टर के लिए, निम्नलिखित घटकों की आवश्यकता होती हैः
जैसा कि देखा जा सकता है, इस बैकटेस्टर में पोर्टफोलियो/जोखिम प्रबंधन, निष्पादन हैंडलिंग (यानी कोई सीमा आदेश नहीं) का कोई संदर्भ शामिल नहीं है और न ही यह लेनदेन लागत का परिष्कृत मॉडलिंग प्रदान करेगा। यह इस चरण में बहुत समस्या नहीं है। यह हमें ऑब्जेक्ट-उन्मुख बैकटेस्टर और पांडास/नमपी पुस्तकालयों के निर्माण की प्रक्रिया से परिचित होने की अनुमति देता है। समय के साथ इसे बेहतर बनाया जाएगा।
अब हम प्रत्येक वस्तु के लिए कार्यान्वयन की रूपरेखा तैयार करेंगे।
इस चरण में रणनीति ऑब्जेक्ट काफी सामान्य होना चाहिए, क्योंकि यह पूर्वानुमान, औसत-वापसी, गति और अस्थिरता रणनीतियों को संभाल रहा होगा। यहां पर विचार की जा रही रणनीतियाँ हमेशा समय श्रृंखला आधारित होंगी, अर्थात
रणनीति वर्ग हमेशा संकेत सिफारिशें भी उत्पन्न करेगा। इसका मतलब है कि यह एक पोर्टफोलियो उदाहरण को लंबी / छोटी या स्थिति रखने के अर्थ में सलाह देगा। यह लचीलापन हमें कई रणनीति
वर्गों के इंटरफ़ेस को एक अमूर्त आधार वर्ग पद्धति का उपयोग करके लागू किया जाएगा। एक अमूर्त आधार वर्ग एक वस्तु है जिसे उदाहरण नहीं दिया जा सकता है और इस प्रकार केवल व्युत्पन्न वर्ग बनाए जा सकते हैं। पायथन कोड नीचे एक फ़ाइल में दिया गया है जिसे कहा जाता हैbacktest.py. रणनीति वर्ग की आवश्यकता है कि किसी भी उपवर्ग को generate_signals विधि लागू करें.
इस वर्ग को सीधे (क्योंकि यह अमूर्त है) से इंस्टैंट किए जाने से रोकने के लिए, एबीसी मॉड्यूल से एबीसीमेटा और अमूर्त विधि वस्तुओं का उपयोग करना आवश्यक है। हम वर्ग की एक संपत्ति सेट करते हैं, जिसे कहा जाता हैमेटाक्लासABCMeta के बराबर होने के लिए और फिर abstractmethod decorator के साथ generate_signals विधि को सजाने के लिए।
# backtest.py
from abc import ABCMeta, abstractmethod
class Strategy(object):
"""Strategy is an abstract base class providing an interface for
all subsequent (inherited) trading strategies.
The goal of a (derived) Strategy object is to output a list of signals,
which has the form of a time series indexed pandas DataFrame.
In this instance only a single symbol/instrument is supported."""
__metaclass__ = ABCMeta
@abstractmethod
def generate_signals(self):
"""An implementation is required to return the DataFrame of symbols
containing the signals to go long, short or hold (1, -1 or 0)."""
raise NotImplementedError("Should implement generate_signals()!")
जबकि उपरोक्त इंटरफ़ेस सीधा है, यह अधिक जटिल हो जाएगा जब यह वर्ग प्रत्येक विशिष्ट प्रकार की रणनीति के लिए विरासत में मिलता है। अंततः इस सेटिंग में रणनीति वर्ग का लक्ष्य पोर्टफोलियो में भेजे जाने वाले प्रत्येक उपकरण के लिए लंबे / छोटे / होल्ड संकेतों की एक सूची प्रदान करना है।
पोर्टफोलियो वर्ग वह है जहां अधिकांश ट्रेडिंग तर्क निवास करेंगे। इस शोध बैकटेस्टर के लिए पोर्टफोलियो स्थिति आकार निर्धारण, जोखिम विश्लेषण, लेनदेन लागत प्रबंधन और निष्पादन हैंडलिंग (यानी बाजार-खुले, बाजार-बंद आदेश) निर्धारित करने के लिए जिम्मेदार है। बाद के चरण में इन कार्यों को अलग-अलग घटकों में विभाजित किया जाएगा। अभी वे एक वर्ग में रोल किए जाएंगे।
यह वर्ग पांडा का व्यापक उपयोग करता है और एक महान उदाहरण प्रदान करता है जहां पुस्तकालय बहुत समय बचा सकता है, विशेष रूप से
पोर्टफोलियो वर्ग का लक्ष्य अंततः ट्रेडों का एक अनुक्रम और एक इक्विटी वक्र का उत्पादन करना है, जिसका विश्लेषण प्रदर्शन वर्ग द्वारा किया जाएगा। इसे प्राप्त करने के लिए इसे एक रणनीति ऑब्जेक्ट से ट्रेडिंग सिफारिशों की एक सूची प्रदान की जानी चाहिए। बाद में, यह रणनीति ऑब्जेक्ट का एक समूह होगा।
पोर्टफोलियो वर्ग को यह बताने की आवश्यकता होगी कि व्यापार संकेतों के एक विशेष सेट के लिए पूंजी को कैसे तैनात किया जाना है, लेनदेन लागतों को कैसे संभालना है और किस प्रकार के आदेशों का उपयोग किया जाएगा। रणनीति वस्तु डेटा के सलाखों पर काम कर रही है और इसलिए एक आदेश के निष्पादन पर प्राप्त कीमतों के संबंध में धारणाएं बनाई जानी चाहिए। चूंकि किसी भी पट्टी की उच्च/निम्न कीमत पूर्व ज्ञात नहीं है, इसलिए व्यापार के लिए केवल खुले और बंद कीमतों का उपयोग करना संभव है। वास्तव में यह गारंटी देना असंभव है कि एक ऑर्डर इन विशेष कीमतों में से एक पर भरा जाएगा जब एक बाजार आदेश का उपयोग किया जाता है, इसलिए यह, सबसे अच्छा, एक अनुमान होगा।
आदेशों को पूरा करने के बारे में मान्यताओं के अलावा, यह बैकटेस्टर मार्जिन / ब्रोकरेज बाधाओं की सभी अवधारणाओं को अनदेखा करेगा और यह मान लेगा कि किसी भी साधन में किसी भी तरलता बाधाओं के बिना स्वतंत्र रूप से लंबा और छोटा जाना संभव है। यह स्पष्ट रूप से एक बहुत ही अवास्तविक धारणा है, लेकिन यह एक है जिसे बाद में ढीला किया जा सकता है।
निम्नलिखित सूची जारी हैbacktest.py:
# backtest.py
class Portfolio(object):
"""An abstract base class representing a portfolio of
positions (including both instruments and cash), determined
on the basis of a set of signals provided by a Strategy."""
__metaclass__ = ABCMeta
@abstractmethod
def generate_positions(self):
"""Provides the logic to determine how the portfolio
positions are allocated on the basis of forecasting
signals and available cash."""
raise NotImplementedError("Should implement generate_positions()!")
@abstractmethod
def backtest_portfolio(self):
"""Provides the logic to generate the trading orders
and subsequent equity curve (i.e. growth of total equity),
as a sum of holdings and cash, and the bar-period returns
associated with this curve based on the 'positions' DataFrame.
Produces a portfolio object that can be examined by
other classes/functions."""
raise NotImplementedError("Should implement backtest_portfolio()!")
इस चरण में रणनीति और पोर्टफोलियो सार आधार वर्गों को पेश किया गया है। अब हम एक कामकाजी "खिलौना रणनीति" का उत्पादन करने के लिए इन वर्गों के कुछ ठोस व्युत्पन्न कार्यान्वयन उत्पन्न करने की स्थिति में हैं।
हम RandomForecastStrategy नामक रणनीति के एक उपवर्ग को उत्पन्न करके शुरू करेंगे, जिसका एकमात्र कार्य यादृच्छिक रूप से चुने गए लंबे / लघु संकेतों का उत्पादन करना है! जबकि यह स्पष्ट रूप से एक बेतुकी ट्रेडिंग रणनीति है, यह ऑब्जेक्ट उन्मुख बैकटेस्टिंग फ्रेमवर्क का प्रदर्शन करके हमारी जरूरतों को पूरा करेगा। इस प्रकार हम random_forecast.py नामक एक नई फ़ाइल शुरू करेंगे, जिसमें यादृच्छिक पूर्वानुमान के लिए सूची निम्नानुसार हैः
# random_forecast.py
import numpy as np
import pandas as pd
import Quandl # Necessary for obtaining financial data easily
from backtest import Strategy, Portfolio
class RandomForecastingStrategy(Strategy):
"""Derives from Strategy to produce a set of signals that
are randomly generated long/shorts. Clearly a nonsensical
strategy, but perfectly acceptable for demonstrating the
backtesting infrastructure!"""
def __init__(self, symbol, bars):
"""Requires the symbol ticker and the pandas DataFrame of bars"""
self.symbol = symbol
self.bars = bars
def generate_signals(self):
"""Creates a pandas DataFrame of random signals."""
signals = pd.DataFrame(index=self.bars.index)
signals['signal'] = np.sign(np.random.randn(len(signals)))
# The first five elements are set to zero in order to minimise
# upstream NaN errors in the forecaster.
signals['signal'][0:5] = 0.0
return signals
अब जब हमारे पास एक
पोर्टफोलियो ऑब्जेक्ट, हालांकि अपने इंटरफेस में बेहद लचीला है, लेनदेन की लागत, बाजार आदेश आदि को संभालने के तरीके के संबंध में विशिष्ट विकल्पों की आवश्यकता होती है। इस बुनियादी उदाहरण में मैंने माना है कि बिना किसी प्रतिबंध या मार्जिन के आसानी से एक साधन को लंबा/लघु करना, सीधे बार के खुले मूल्य पर खरीदना या बेचना, शून्य लेनदेन लागत (स्लिप, शुल्क और बाजार प्रभाव को शामिल करना) और प्रत्येक व्यापार के लिए सीधे खरीदने के लिए स्टॉक की मात्रा निर्दिष्ट करना संभव होगा।
यहाँ random_forecast.py सूची की निरंतरता हैः
# random_forecast.py
class MarketOnOpenPortfolio(Portfolio):
"""Inherits Portfolio to create a system that purchases 100 units of
a particular symbol upon a long/short signal, assuming the market
open price of a bar.
In addition, there are zero transaction costs and cash can be immediately
borrowed for shorting (no margin posting or interest requirements).
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):
"""Creates a 'positions' DataFrame that simply longs or shorts
100 of the particular symbol based on the forecast signals of
{1, 0, -1} from the signals DataFrame."""
positions = pd.DataFrame(index=signals.index).fillna(0.0)
positions[self.symbol] = 100*signals['signal']
return positions
def backtest_portfolio(self):
"""Constructs a portfolio from the positions DataFrame by
assuming the ability to trade at the precise market open price
of each bar (an unrealistic assumption!).
Calculates the total of cash and the holdings (market price of
each position per bar), in order to generate an equity curve
('total') and a set of bar-based returns ('returns').
Returns the portfolio object to be used elsewhere."""
# Construct the portfolio DataFrame to use the same index
# as 'positions' and with a set of 'trading orders' in the
# 'pos_diff' object, assuming market open prices.
portfolio = self.positions*self.bars['Open']
pos_diff = self.positions.diff()
# Create the 'holdings' and 'cash' series by running through
# the trades and adding/subtracting the relevant quantity from
# each column
portfolio['holdings'] = (self.positions*self.bars['Open']).sum(axis=1)
portfolio['cash'] = self.initial_capital - (pos_diff*self.bars['Open']).sum(axis=1).cumsum()
# Finalise the total and bar-based returns based on the 'cash'
# and 'holdings' figures for the portfolio
portfolio['total'] = portfolio['cash'] + portfolio['holdings']
portfolio['returns'] = portfolio['total'].pct_change()
return portfolio
यह हमें सब कुछ हम एक ऐसी प्रणाली पर आधारित एक इक्विटी वक्र उत्पन्न करने के लिए की जरूरत है देता है। अंतिम चरण एक साथ यह सब बांधना हैमुख्यकार्य:
if __name__ == "__main__":
# Obtain daily bars of SPY (ETF that generally
# follows the S&P500) from Quandl (requires 'pip install Quandl'
# on the command line)
symbol = 'SPY'
bars = Quandl.get("GOOG/NYSE_%s" % symbol, collapse="daily")
# Create a set of random forecasting signals for SPY
rfs = RandomForecastingStrategy(symbol, bars)
signals = rfs.generate_signals()
# Create a portfolio of SPY
portfolio = MarketOnOpenPortfolio(symbol, bars, signals, initial_capital=100000.0)
returns = portfolio.backtest_portfolio()
print returns.tail(10)
कार्यक्रम का आउटपुट निम्नानुसार है. आपका चयनित दिनांक सीमा और प्रयोग किए गए यादृच्छिक बीज के आधार पर नीचे दिए गए आउटपुट से भिन्न होगा:
SPY holdings cash total returns
तिथि
2014-01-02 -18398 -18398 111486 93088 0.000097
2014-01-03 18321 18321 74844 93165 0.000827
2014-01-06 18347 18347 74844 93191 0.000279
2014-01-07 18309 18309 74844 93153 -0.000408
2014-01-08 -18345 -18345 111534 93189 0.000386
2014-01-09 -18410 -18410 111534 93124 -0.000698
2014-01-10 -18395 -18395 111534 93139 0.000161
2014-01-13 -18371 -18371 111534 93163 0.000258
2014-01-14 -18228 -18228 111534 93306 0.001535
2014-01-15 18410 18410 74714 93124 -0.001951
इस उदाहरण में रणनीति ने पैसा खो दिया, जो पूर्वानुमानकर्ता की स्टोकैस्टिक प्रकृति को देखते हुए आश्चर्य की बात नहीं है! अगले कदम एक प्रदर्शन वस्तु बनाना है जो एक पोर्टफोलियो उदाहरण को स्वीकार करता है और प्रदर्शन मीट्रिक की एक सूची प्रदान करता है जिस पर रणनीति को फ़िल्टर करने या नहीं करने के निर्णय का आधार है।
हम पोर्टफोलियो ऑब्जेक्ट में भी सुधार कर सकते हैं ताकि लेनदेन लागतों (जैसे इंटरएक्टिव ब्रोकर कमीशन और फिसलन) का अधिक यथार्थवादी प्रबंधन हो सके। हम सीधे तौर पर एक पूर्वानुमान इंजन को रणनीति ऑब्जेक्ट में शामिल कर सकते हैं, जो (उम्मीद है) बेहतर परिणाम देगा। निम्नलिखित लेखों में हम इन अवधारणाओं का अधिक गहराई से पता लगाएंगे।