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     )