ml-finance-python
python scripts for finance machine learning
git clone https://9o.is/git/ml-finance-python.git
long_short_equity_template.py
(7834B)
1 """This algorithm demonstrates the concept of long-short equity.
2 It uses two fundamental factors to rank equities in our universe.
3 It then longs the top of the ranking and shorts the bottom.
4 For information on long-short equity strategies, please see the corresponding
5 lecture on our lectures page:
6
7 https://www.quantopian.com/lectures
8
9 WARNING: These factors were selected because they worked in the past over the specific time period we choose.
10 We do not anticipate them working in the future. In practice finding your own factors is the hardest
11 part of developing any long-short equity strategy. This algorithm is meant to serve as a framework for testing your own ranking factors.
12
13 This algorithm was developed as part of
14 Quantopian's Lecture Series. Please direct any
15 questions, feedback, or corrections to max@quantopian.com
16 """
17
18 import numpy as np
19 import pandas as pd
20 import quantopian.algorithm as algo
21 import quantopian.optimize as opt
22 from quantopian.pipeline import Pipeline
23 from quantopian.pipeline.factors import CustomFactor, SimpleMovingAverage
24
25 from quantopian.pipeline.filters import QTradableStocksUS
26 from quantopian.pipeline.experimental import risk_loading_pipeline
27
28 from quantopian.pipeline.data.builtin import USEquityPricing
29 from quantopian.pipeline.data.zacks import broker_ratings
30 from quantopian.pipeline.data.psychsignal import stocktwits
31
32 # Constraint Parameters
33 MAX_GROSS_LEVERAGE = 1.0
34 NUM_LONG_POSITIONS = 300
35 NUM_SHORT_POSITIONS = 300
36
37 # Here we define the maximum position size that can be held for any
38 # given stock. If you have a different idea of what these maximum
39 # sizes should be, feel free to change them. Keep in mind that the
40 # optimizer needs some leeway in order to operate. Namely, if your
41 # maximum is too small, the optimizer may be overly-constrained.
42 MAX_SHORT_POSITION_SIZE = 2.0*1.0/(NUM_LONG_POSITIONS+NUM_SHORT_POSITIONS)
43 MAX_LONG_POSITION_SIZE = 2.0*1.0/(NUM_LONG_POSITIONS+NUM_SHORT_POSITIONS)
44
45 def make_pipeline():
46 """
47 Create and return our pipeline.
48
49 We break this piece of logic out into its own function to make it easier to
50 test and modify in isolation.
51
52 In particular, this function can be copy/pasted into research and run by itself.
53 """
54
55 # The factors we create here are based on broker recommendations data and a moving
56 # average of sentiment data
57 diff = (
58 broker_ratings.rating_cnt_strong_buys.latest+broker_ratings.rating_cnt_mod_buys.latest -
59 (broker_ratings.rating_cnt_strong_sells.latest+broker_ratings.rating_cnt_mod_sells.latest)
60 )
61 # Here we temper the diff between recommended buys and sells with a ratio of what
62 # percentage of brokers actually rated a given security
63 rat = broker_ratings.rating_cnt_with.latest/ \
64 (broker_ratings.rating_cnt_with.latest+broker_ratings.rating_cnt_without.latest)
65 alpha_signal = diff*rat
66
67 sentiment_score = SimpleMovingAverage(
68 inputs=[stocktwits.bull_minus_bear],
69 window_length=3,
70 )
71
72 universe = QTradableStocksUS()
73
74 # Construct a Factor representing the rank of each asset by our value
75 # quality metrics. We aggregate them together here using simple addition
76 # after zscore-ing them
77 combined_factor = (
78 alpha_signal.zscore() + sentiment_score.zscore()
79 )
80
81 # Build Filters representing the top and bottom NUM_POSITIONS stocks by our combined ranking system.
82 # We'll use these as our tradeable universe each day.
83 longs = combined_factor.top(NUM_LONG_POSITIONS, mask=universe)
84 shorts = combined_factor.bottom(NUM_SHORT_POSITIONS, mask=universe)
85
86 # The final output of our pipeline should only include
87 # the top/bottom 300 stocks by our criteria
88 long_short_screen = (longs | shorts)
89
90 # Create pipeline
91 pipe = Pipeline(
92 columns = {
93 'longs':longs,
94 'shorts':shorts,
95 'combined_factor':combined_factor
96 },
97 screen = long_short_screen
98 )
99 return pipe
100
101
102 def initialize(context):
103 # Here we set our slippage and commisions. Set slippage
104 # and commission to zero to evaulate the signal-generating
105 # ability of the algorithm independent of these additional
106 # costs.
107 set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))
108 set_slippage(slippage.VolumeShareSlippage(volume_limit=1, price_impact=0))
109 context.spy = sid(8554)
110
111 algo.attach_pipeline(make_pipeline(), 'long_short_equity_template')
112
113 # attach the pipeline for the risk model factors that we
114 # want to neutralize in the optimization step
115 algo.attach_pipeline(risk_loading_pipeline(), 'risk_factors')
116
117 # Schedule my rebalance function
118 schedule_function(func=rebalance,
119 date_rule=date_rules.every_day(),
120 time_rule=time_rules.market_open(hours=0,minutes=30),
121 half_days=True)
122 # record my portfolio variables at the end of day
123 schedule_function(func=recording_statements,
124 date_rule=date_rules.every_day(),
125 time_rule=time_rules.market_close(),
126 half_days=True)
127
128
129 def before_trading_start(context, data):
130 # Call algo.pipeline_output to get the output
131 # Note: this is a dataframe where the index is the SIDs for all
132 # securities to pass my screen and the columns are the factors
133 # added to the pipeline object above
134 context.pipeline_data = algo.pipeline_output('long_short_equity_template')
135
136 # This dataframe will contain all of our risk loadings
137 context.risk_loadings = algo.pipeline_output('risk_factors')
138
139 def recording_statements(context, data):
140 # Plot the number of positions over time.
141 record(num_positions=len(context.portfolio.positions))
142
143
144 # Called at the start of every month in order to rebalance
145 # the longs and shorts lists
146 def rebalance(context, data):
147 ### Optimize API
148 pipeline_data = context.pipeline_data
149
150 risk_loadings = context.risk_loadings
151
152 ### Here we define our objective for the Optimize API. We have
153 # selected MaximizeAlpha because we believe our combined factor
154 # ranking to be proportional to expected returns. This routine
155 # will optimize the expected return of our algorithm, going
156 # long on the highest expected return and short on the lowest.
157 objective = opt.MaximizeAlpha(pipeline_data.combined_factor)
158
159 ### Define the list of constraints
160 constraints = []
161 # Constrain our maximum gross leverage
162 constraints.append(opt.MaxGrossExposure(MAX_GROSS_LEVERAGE))
163
164 # Require our algorithm to remain dollar neutral
165 constraints.append(opt.DollarNeutral())
166
167 # Add the RiskModelExposure constraint to make use of the
168 # default risk model constraints
169 neutralize_risk_factors = opt.experimental.RiskModelExposure(
170 risk_model_loadings=risk_loadings
171 )
172 constraints.append(neutralize_risk_factors)
173
174 # With this constraint we enforce that no position can make up
175 # greater than MAX_SHORT_POSITION_SIZE on the short side and
176 # no greater than MAX_LONG_POSITION_SIZE on the long side. This
177 # ensures that we do not overly concentrate our portfolio in
178 # one security or a small subset of securities.
179 constraints.append(
180 opt.PositionConcentration.with_equal_bounds(
181 min=-MAX_SHORT_POSITION_SIZE,
182 max=MAX_LONG_POSITION_SIZE
183 ))
184
185 ### Put together all the pieces we defined above by passing
186 # them into the algo.order_optimal_portfolio function. This handles
187 # all of our ordering logic, assigning appropriate weights
188 # to the securities in our universe to maximize our alpha with
189 # respect to the given constraints.
190 algo.order_optimal_portfolio(
191 objective=objective,
192 constraints=constraints
193 )