ml-finance-python
python scripts for finance machine learning
git clone https://9o.is/git/ml-finance-python.git
pairs_trading_optimize_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