পাণ্ডাসহ পাইথনে একটি চলমান গড় ক্রসওভার ব্যাকটেস্টিং

লেখক:ভাল, তৈরিঃ 2019-03-27 15:11:40, আপডেটঃ

এই প্রবন্ধে আমরা একটি প্রকৃত কৌশল, যথা AAPL এর চলমান গড় ক্রসওভার নিয়ে গবেষণা করার জন্য আমরা যে যন্ত্রপাতিটি চালু করেছি তা ব্যবহার করব।

চলমান গড় ক্রসওভার কৌশল

মুভিং এভারেজ ক্রসওভার কৌশল একটি অত্যন্ত সুপরিচিত সরলীকৃত গতি কৌশল। এটি প্রায়শই পরিমাণগত ব্যবসায়ের জন্য হ্যালো ওয়ার্ল্ড উদাহরণ হিসাবে বিবেচিত হয়।

এখানে উল্লিখিত কৌশলটি কেবলমাত্র দীর্ঘ। দুটি পৃথক সহজ চলমান গড় ফিল্টার তৈরি করা হয়, একটি নির্দিষ্ট সময় সিরিজের পরিবর্তিত লুকব্যাক সময়কাল সহ। সম্পদ কেনার সংকেতগুলি ঘটে যখন স্বল্পতর লুকব্যাক চলমান গড় দীর্ঘতর লুকব্যাক চলমান গড়ের চেয়ে বেশি হয়। যদি দীর্ঘতর গড়টি পরে স্বল্পতর গড়ের চেয়ে বেশি হয় তবে সম্পদটি আবার বিক্রি করা হয়। কৌশলটি ভাল কাজ করে যখন একটি সময় সিরিজ শক্তিশালী প্রবণতার সময়কালে প্রবেশ করে এবং তারপরে ধীরে ধীরে প্রবণতা বিপরীত করে।

এই উদাহরণে, আমি অ্যাপল, ইনকর্পোরেটেড (এএপিএল) কে সময় সিরিজ হিসাবে বেছে নিয়েছি, 100 দিনের একটি সংক্ষিপ্ত লুকব্যাক এবং 400 দিনের একটি দীর্ঘ লুকব্যাক সহ। এটি জিপলাইন অ্যালগরিদমিক ট্রেডিং লাইব্রেরি দ্বারা প্রদত্ত উদাহরণ। সুতরাং যদি আমরা আমাদের নিজস্ব ব্যাকটেস্টার বাস্তবায়ন করতে চাই তবে আমাদের নিশ্চিত করতে হবে যে এটি জিপলাইনের ফলাফলগুলির সাথে মেলে, যাচাইয়ের একটি প্রাথমিক উপায় হিসাবে।

বাস্তবায়ন

এখানে পূর্ববর্তী টিউটোরিয়ালটি অনুসরণ করতে ভুলবেন না, যা ব্যাকটেস্টারের জন্য প্রাথমিক অবজেক্ট শ্রেণিবিন্যাস কীভাবে নির্মিত হয় তা বর্ণনা করে, অন্যথায় নীচের কোডটি কাজ করবে না। এই বিশেষ বাস্তবায়নের জন্য আমি নিম্নলিখিত লাইব্রেরিগুলি ব্যবহার করেছিঃ

  • পাইথন - ২.৭.৩
  • NumPy - 1.8.0
  • পান্ডা - 0.12.0
  • matplotlib - 1.1.0

ma_cross.py বাস্তবায়নের জন্য প্রয়োজনbacktest.pyপূর্ববর্তী টিউটোরিয়াল থেকে. প্রথম ধাপ প্রয়োজনীয় মডিউল এবং বস্তু আমদানি করা হয়ঃ

# ma_cross.py

import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from pandas.io.data import DataReader
from backtest import Strategy, Portfolio

পূর্ববর্তী টিউটোরিয়ালে যেমন আমরা কৌশল বিমূর্ত বেস ক্লাসকে সাবক্লাস করতে যাচ্ছি MovingAverageCrossStrategy তৈরি করতে, যা AAPL এর চলমান গড়গুলি একে অপরের উপর ক্রস করার সময় সংকেতগুলি কীভাবে তৈরি করা যায় তার সমস্ত বিবরণ ধারণ করে।

বস্তুর একটি short_window এবং একটি long_window প্রয়োজন যার উপর কাজ করতে হবে। মানগুলি যথাক্রমে 100 দিন এবং 400 দিনের ডিফল্ট হিসাবে সেট করা হয়েছে, যা জিপলাইন এর প্রধান উদাহরণে ব্যবহৃত একই পরামিতি।

মুভিং গড়গুলি এএপিএল স্টকগুলির বারগুলিতে [Close] বন্ধের মূল্যে পান্ডা রোলিং_মিড ফাংশন ব্যবহার করে তৈরি করা হয়। একবার পৃথক চলমান গড়গুলি তৈরি হয়ে গেলে, সংক্ষিপ্ত চলমান গড়টি দীর্ঘ চলমান গড়ের চেয়ে বড় হলে বা অন্যথায় 0.0 এর সমান কলামটি সেট করে সিগন্যাল সিরিজ তৈরি করা হয়। এর থেকে ট্রেডিং সংকেত উপস্থাপন করার জন্য অবস্থান অর্ডারগুলি তৈরি করা যেতে পারে।

# ma_cross.py

class MovingAverageCrossStrategy(Strategy):
    """    
    Requires:
    symbol - A stock symbol on which to form a strategy on.
    bars - A DataFrame of bars for the above symbol.
    short_window - Lookback period for short moving average.
    long_window - Lookback period for long moving average."""

    def __init__(self, symbol, bars, short_window=100, long_window=400):
        self.symbol = symbol
        self.bars = bars

        self.short_window = short_window
        self.long_window = long_window

    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

        # Create the set of short and long simple moving averages over the 
        # respective periods
        signals['short_mavg'] = pd.rolling_mean(bars['Close'], self.short_window, min_periods=1)
        signals['long_mavg'] = pd.rolling_mean(bars['Close'], self.long_window, min_periods=1)

        # Create a 'signal' (invested or not invested) when the short moving average crosses the long
        # moving average, but only for the period greater than the shortest moving average window
        signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:] 
            > signals['long_mavg'][self.short_window:], 1.0, 0.0)   

        # Take the difference of the signals in order to generate actual trading orders
        signals['positions'] = signals['signal'].diff()   

        return signals

মার্কেট অন ক্লোজপোর্টফোলিওকে পোর্টফোলিও থেকে উপবিভাগ করা হয়েছে, যা পাওয়া যায়backtest.py. এটি পূর্ববর্তী টিউটোরিয়ালে বর্ণিত বাস্তবায়নের সাথে প্রায় অভিন্ন, তবে ব্যবসায়গুলি এখন ওপেন-টু-ওপেন ভিত্তিতে নয়, ক্লোজ-টু-ক্লোজ ভিত্তিতে পরিচালিত হয়। পোর্টফোলিও অবজেক্টটি কীভাবে সংজ্ঞায়িত করা হয় সে সম্পর্কে বিস্তারিত জানার জন্য, পূর্ববর্তী টিউটোরিয়ালটি দেখুন। আমি সম্পূর্ণতার জন্য কোডটি রেখেছি এবং এই টিউটোরিয়ালটি স্বয়ংসম্পূর্ণ রাখতেঃ

# ma_cross.py

class MarketOnClosePortfolio(Portfolio):
    """Encapsulates the notion of a portfolio of positions based
    on a set of signals as provided by a Strategy.

    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):
        positions = pd.DataFrame(index=signals.index).fillna(0.0)
        positions[self.symbol] = 100*signals['signal']   # This strategy buys 100 shares
        return positions
                    
    def backtest_portfolio(self):
        portfolio = self.positions*self.bars['Close']
        pos_diff = self.positions.diff()

        portfolio['holdings'] = (self.positions*self.bars['Close']).sum(axis=1)
        portfolio['cash'] = self.initial_capital - (pos_diff*self.bars['Close']).sum(axis=1).cumsum()

        portfolio['total'] = portfolio['cash'] + portfolio['holdings']
        portfolio['returns'] = portfolio['total'].pct_change()
        return portfolio

এখন যেহেতু MovingAverageCrossStrategy এবং MarketOnClosePortfolio ক্লাসগুলি সংজ্ঞায়িত করা হয়েছে, একটিপ্রধানএই কৌশলটি মূলধন কার্ভের একটি গ্রাফের মাধ্যমে পর্যালোচনা করা হবে।

পান্ডা ডেটা রিডার অবজেক্টটি ১৯৯০ সালের ১ জানুয়ারি থেকে ২০০২ সালের ১ জানুয়ারি পর্যন্ত সময়কালের জন্য এএপিএল স্টকগুলির ওএইচএলসিভি দাম ডাউনলোড করে, এই সময়ে দীর্ঘ-কেবল সংকেতগুলি তৈরি করতে ডেটাফ্রেম সংকেতগুলি তৈরি করা হয়। পরবর্তীকালে পোর্টফোলিওটি ১০০,০০০ মার্কিন ডলার প্রাথমিক মূলধন ভিত্তিতে উত্পন্ন হয় এবং রিটার্নগুলি ইক্যুইটি বক্ররেখায় গণনা করা হয়।

শেষ ধাপটি হ'ল উভয় এএপিএল দামের একটি দ্বি-সংখ্যার গ্রাফ প্লট করা, যা চলমান গড় এবং কিনুন / বিক্রয় সংকেতগুলির সাথে ওভারলেড, পাশাপাশি একই কিনুন / বিক্রয় সংকেতগুলির সাথে ইক্যুইটি বক্ররেখা। প্লটিং কোডটি জিপলাইন বাস্তবায়ন উদাহরণ থেকে নেওয়া হয়েছে (এবং সংশোধন করা হয়েছে) ।

# ma_cross.py

if __name__ == "__main__":
    # Obtain daily bars of AAPL from Yahoo Finance for the period
    # 1st Jan 1990 to 1st Jan 2002 - This is an example from ZipLine
    symbol = 'AAPL'
    bars = DataReader(symbol, "yahoo", datetime.datetime(1990,1,1), datetime.datetime(2002,1,1))

    # Create a Moving Average Cross Strategy instance with a short moving
    # average window of 100 days and a long window of 400 days
    mac = MovingAverageCrossStrategy(symbol, bars, short_window=100, long_window=400)
    signals = mac.generate_signals()

    # Create a portfolio of AAPL, with $100,000 initial capital
    portfolio = MarketOnClosePortfolio(symbol, bars, signals, initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    # Plot two charts to assess trades and equity curve
    fig = plt.figure()
    fig.patch.set_facecolor('white')     # Set the outer colour to white
    ax1 = fig.add_subplot(211,  ylabel='Price in $')
    
    # Plot the AAPL closing price overlaid with the moving averages
    bars['Close'].plot(ax=ax1, color='r', lw=2.)
    signals[['short_mavg', 'long_mavg']].plot(ax=ax1, lw=2.)

    # Plot the "buy" trades against AAPL
    ax1.plot(signals.ix[signals.positions == 1.0].index, 
             signals.short_mavg[signals.positions == 1.0],
             '^', markersize=10, color='m')

    # Plot the "sell" trades against AAPL
    ax1.plot(signals.ix[signals.positions == -1.0].index, 
             signals.short_mavg[signals.positions == -1.0],
             'v', markersize=10, color='k')

    # Plot the equity curve in dollars
    ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
    returns['total'].plot(ax=ax2, lw=2.)

    # Plot the "buy" and "sell" trades against the equity curve
    ax2.plot(returns.ix[signals.positions == 1.0].index, 
             returns.total[signals.positions == 1.0],
             '^', markersize=10, color='m')
    ax2.plot(returns.ix[signals.positions == -1.0].index, 
             returns.total[signals.positions == -1.0],
             'v', markersize=10, color='k')

    # Plot the figure
    fig.show()

কোডের গ্রাফিকাল আউটপুট নিম্নরূপ। আমি উবুন্টুতে থাকাকালীন সরাসরি আইপিথন কনসোলে এটি রাখতে আইপিথন % পেস্ট কমান্ডটি ব্যবহার করেছি, যাতে গ্রাফিকাল আউটপুটটি দৃশ্যমান থাকে। গোলাপী আপটিক্স স্টক কেনার প্রতিনিধিত্ব করে, যখন কালো ডাউনটিক্স এটি আবার বিক্রি করে:imgAAPL Moving Average ক্রসওভার পারফরম্যান্স 1990-01-01 থেকে 2002-01-01 পর্যন্ত

যেমনটি দেখা যায়, কৌশলটি এই সময়ের মধ্যে অর্থ হারাচ্ছে, পাঁচটি ওভার-ট্রিপ ট্রেড রয়েছে। এই সময়কালে AAPL এর আচরণ, যা 1998 সাল থেকে শুরু হওয়া একটি সামান্য নেমে যাওয়া প্রবণতা অনুসরণ করেছিল, এটি অবাক হওয়ার মতো নয়। চলমান গড় সংকেতগুলির পুনর্বিবেচনা সময়টি বেশ বড় এবং এটি চূড়ান্ত ব্যবসায়ের লাভকে প্রভাবিত করেছিল, যা অন্যথায় কৌশলটিকে লাভজনক করে তুলতে পারে।

পরবর্তী নিবন্ধে আমরা পারফরম্যান্স বিশ্লেষণের জন্য একটি আরো পরিশীলিত উপায় তৈরি করব, পাশাপাশি পৃথক চলমান গড় সংকেতগুলির পুনর্বিবেচনার সময়কালকে কীভাবে অপ্টিমাইজ করা যায় তা বর্ণনা করব।


আরো