ml-finance-python

python scripts for finance machine learning

git clone https://9o.is/git/ml-finance-python.git

pairs_trading_template.py

(6010B)


      1 import numpy as np
      2 import statsmodels.api as sm
      3 import pandas as pd
      4 
      5 import quantopian.experimental.optimize as opt
      6 import quantopian.algorithm as algo
      7 
      8 def initialize(context):
      9     # Quantopian backtester specific variables
     10     set_slippage(slippage.FixedSlippage(spread=0))
     11     set_commission(commission.PerTrade(cost=1))
     12     set_symbol_lookup_date('2014-01-01')
     13     
     14     context.stock_pairs = [(symbol('ABGB'), symbol('FSLR')),
     15                            (symbol('CSUN'), symbol('ASTI'))]
     16     
     17     context.stocks = symbols('ABGB', 'FSLR', 'CSUN', 'ASTI')
     18     
     19     context.num_pairs = len(context.stock_pairs)
     20     # strategy specific variables
     21     context.lookback = 20 # used for regression
     22     context.z_window = 20 # used for zscore calculation, must be <= lookback
     23     
     24     context.target_weights = pd.Series(index=context.stocks, data=0.25)
     25     
     26     context.spread = np.ndarray((context.num_pairs, 0))
     27     context.inLong = [False] * context.num_pairs
     28     context.inShort = [False] * context.num_pairs
     29     
     30     # Only do work 30 minutes before close
     31     schedule_function(func=check_pair_status, date_rule=date_rules.every_day(), time_rule=time_rules.market_close(minutes=30))
     32     
     33 # Will be called on every trade event for the securities you specify. 
     34 def handle_data(context, data):
     35     # Our work is now scheduled in check_pair_status
     36     pass
     37 
     38 def check_pair_status(context, data):
     39     
     40     prices = data.history(context.stocks, 'price', 35, '1d').iloc[-context.lookback::]
     41     
     42     new_spreads = np.ndarray((context.num_pairs, 1))
     43     
     44     for i in range(context.num_pairs):
     45 
     46         (stock_y, stock_x) = context.stock_pairs[i]
     47 
     48         Y = prices[stock_y]
     49         X = prices[stock_x]
     50         
     51         # Comment explaining try block
     52         try:
     53             hedge = hedge_ratio(Y, X, add_const=True)      
     54         except ValueError as e:
     55             log.debug(e)
     56             return
     57 
     58         context.target_weights = get_current_portfolio_weights(context, data)
     59         
     60         new_spreads[i, :] = Y[-1] - hedge * X[-1]
     61 
     62         if context.spread.shape[1] > context.z_window:
     63             # Keep only the z-score lookback period
     64             spreads = context.spread[i, -context.z_window:]
     65 
     66             zscore = (spreads[-1] - spreads.mean()) / spreads.std()
     67 
     68             if context.inShort[i] and zscore < 0.0:
     69                 context.target_weights[stock_y] = 0
     70                 context.target_weights[stock_x] = 0
     71                 
     72                 context.inShort[i] = False
     73                 context.inLong[i] = False
     74                 
     75                 record(X_pct=0, Y_pct=0)
     76                 allocate(context, data)
     77                 return
     78 
     79             if context.inLong[i] and zscore > 0.0:
     80                 context.target_weights[stock_y] = 0
     81                 context.target_weights[stock_x] = 0
     82                 
     83                 
     84                 context.inShort[i] = False
     85                 context.inLong[i] = False
     86                 
     87                 record(X_pct=0, Y_pct=0)
     88                 allocate(context, data)
     89                 return
     90 
     91             if zscore < -1.0 and (not context.inLong[i]):
     92                 # Only trade if NOT already in a trade 
     93                 y_target_shares = 1
     94                 X_target_shares = -hedge
     95                 context.inLong[i] = True
     96                 context.inShort[i] = False
     97 
     98                 (y_target_pct, x_target_pct) = computeHoldingsPct(y_target_shares,X_target_shares, Y[-1], X[-1])
     99                 
    100                 context.target_weights[stock_y] = y_target_pct * (1.0/context.num_pairs)
    101                 context.target_weights[stock_x] = x_target_pct * (1.0/context.num_pairs)
    102                 
    103                 record(Y_pct=y_target_pct, X_pct=x_target_pct)
    104                 allocate(context, data)
    105                 return
    106                 
    107 
    108             if zscore > 1.0 and (not context.inShort[i]):
    109                 # Only trade if NOT already in a trade
    110                 y_target_shares = -1
    111                 X_target_shares = hedge
    112                 context.inShort[i] = True
    113                 context.inLong[i] = False
    114 
    115                 (y_target_pct, x_target_pct) = computeHoldingsPct( y_target_shares, X_target_shares, Y[-1], X[-1] )
    116                 
    117                 context.target_weights[stock_y] = y_target_pct * (1.0/context.num_pairs)
    118                 context.target_weights[stock_x] = x_target_pct * (1.0/context.num_pairs)
    119                 
    120                 record(Y_pct=y_target_pct, X_pct=x_target_pct)
    121                 allocate(context, data)
    122                 return
    123         
    124     context.spread = np.hstack([context.spread, new_spreads])
    125 
    126 def hedge_ratio(Y, X, add_const=True):
    127     if add_const:
    128         X = sm.add_constant(X)
    129         model = sm.OLS(Y, X).fit()
    130         return model.params[1]
    131     model = sm.OLS(Y, X).fit()
    132     return model.params.values
    133    
    134 def computeHoldingsPct(yShares, xShares, yPrice, xPrice):
    135     yDol = yShares * yPrice
    136     xDol = xShares * xPrice
    137     notionalDol =  abs(yDol) + abs(xDol)
    138     y_target_pct = yDol / notionalDol
    139     x_target_pct = xDol / notionalDol
    140     return (y_target_pct, x_target_pct)
    141 
    142 def get_current_portfolio_weights(context, data):  
    143     positions = context.portfolio.positions  
    144     positions_index = pd.Index(positions)  
    145     share_counts = pd.Series(  
    146         index=positions_index,  
    147         data=[positions[asset].amount for asset in positions]  
    148     )
    149 
    150     current_prices = data.current(positions_index, 'price')  
    151     current_weights = share_counts * current_prices / context.portfolio.portfolio_value  
    152     return current_weights.reindex(positions_index.union(context.stocks), fill_value=0.0)  
    153 
    154 def allocate(context, data):    
    155     # Set objective to match target weights as closely as possible, given constraints
    156     objective = opt.TargetPortfolioWeights(context.target_weights)
    157     
    158     # Define constraints
    159     constraints = []
    160     constraints.append(opt.MaxGrossLeverage(1.0))
    161     
    162     
    163     algo.order_optimal_portfolio(objective=objective, constraints=constraints, universe=context.stocks)
    164 
    165 
    166