Anthony Garner started a nice thread [here] about monthly momentum based rotation among stocks.

Back tests by several members show that the strategy can work very well, but its returns very sensitive (~10x) to starting day.

I've started this new thread to discuss the behavior of Garner's general strategy with weekly rotation and several other modifications that were mostly presented in the original thread. The weekly vs monthly behavior is different enough that a new thread is warranted.

The results presented below are for weekly rotation with the following rules applied to stock selection

a) are in the top 3000 by market cap

b) have net gain over the past 252 days

c) have positive cash flow

d) have and average daily dollar volume of at least $500k over the past 60 days

As with the monthly version the strategy is in stocks vs bonds based on fast vs slow SMA of a proxy index. In this case VTI is used vs SPY as the strategy considers the top 3000 stocks and often invests outside of the SP500.

I also disabled the stop loss and profit taking sections of Garner's code, as they should not have much effect in a one week holding period.

offset= 0 total return 1422% Alpha 1.03 Sharpe 4.34 Max DD 36% Vol 0.25

offset= 1 total return 1366% Alpha 0.98 Sharpe 4.17 Max DD 36% Vol 0.25 [back test attached]

offset= 2 total return 2193% Alpha 1.63 Sharpe 6.96 Max DD 32% Vol 0.24

offset= 3 total return 1543% Alpha 1.13 Sharpe 4.81 Max DD 32% Vol 0.24

offset= 4 total return 1794% Alpha 1.32 Sharpe 5.65 Max DD 26% Vol 0.24

This is certainly a more consistent set of results.

From this perhaps others might recommend how to further improve behavior (max drawdown, length of drawdown periods, ...)

I also tried to remove dead code and provide some comments within the code as to intended behavior. At the top of the file are notes related to some quick look trade results.

Hope this is helpful.

[edit: for poor grammar and to replace back test with one that has summary results consistent with this post. Original post had back test that had correct result but was done before I added day_offset summary to my notes in the file header.]

Returns | 1 Month | 3 Month | 6 Month | 12 Month |

Alpha | 1 Month | 3 Month | 6 Month | 12 Month |

Beta | 1 Month | 3 Month | 6 Month | 12 Month |

Sharpe | 1 Month | 3 Month | 6 Month | 12 Month |

Sortino | 1 Month | 3 Month | 6 Month | 12 Month |

Volatility | 1 Month | 3 Month | 6 Month | 12 Month |

Max Drawdown | 1 Month | 3 Month | 6 Month | 12 Month |

""" Adapted from "A simple momentum rotation system for stocks" https://www.quantopian.com/posts/a-simple-momentum-rotation-system-for-stocks PF 2016_0807: The unmodified performance of this algorithm is remarkable from 1/4/03 to 11/30/15 Total Returns 1287% Benchmark 192.5% Max Drawdown 50.4% Alpha 0.87 Beta 0.85 Sharpe 3.25 Volatility 0.30 Method outline is: Buy and hold best 10 of 3000 stocks each month During the month sell big losers (stop loss) and big winners (profit taking) Selection considers - Four momentum factors over 20, 60, 125 and 252 days - Efficiency threshold = 0.031 based on 252 day return vs sum of daily High minus Low **PF 2016_0807: Comments relative to the monthly version of the strategy PF 2016_0807: The original post successfully outlines a method outline of use to the community. There was no attempt to make this a production-ready algorithm, so some problems and uncertain features exist. A few of these impacted my ability to understand what was happening so I tried to resolve them (perhaps only to myself): 1) although this is nominally a long-only algo the daily rebalance can result in shorting 2) liquidity problem even when starting with only $100k. Leverage is roughly 35% to 110%. The number of assets is roughtly 4 to 15 vs defined top-10. 3) the algorithm lacks any logic to exit stocks during prolonged drawdown periods. Real investors would have exited a few times from 2003 through 2015. 4) the utility of the efficiency factor test is not clear. The threshold of 0.031 appears to be oddly low as efficiency could easily be much larger. PF 2016_0807: Below is a summary of what I did to resolve/improve issues 1-3 and my finding that the utility of the efficiency function can be had more simply by requiring the 252-day return (factor_4) to be > 0.0. I tried to leave the rest of the algorithm as is. Performance is evaluated over the same 1/4/2003 to 11/30/2015 period as the original posting. Another tester might investigate other interesting features of Garner's algorithm (ranking periods, profit taking logic, ...) PF 2016_0807: Shorting issue (resolved in one change) I modified the code to issue sell orders for obsolete postions before issuing buy orders for new positions. This has resolved the problem and improved overall return as the stocks being shorted were probably not good shorting candidates. PF 2016_0807: Liquidity problems (resolved in three changes) Leverage often exceeds 1.0 due to an inability to sell obsolete positions in a single trading session. Leverage 1: Add a function to daily rebalance to continue sales of these positions This did drive the leverage down to 1.0 quickly in all but a few cases. As expected the total return also dropped as the average leverage was reduced and more trade fees were paid. Total Returns 1163% Benchmark 192.5% Max Drawdown 52.1% Alpha 0.78 Beta 0.88 Sharpe 2.85 Volatility 0.31 PF 2016_0807: Leverage 2: Add Average Daily Dollar Volume (ADDV) as a filter factor. Consider only stocks with ADDV > $500k over the past 20 days This nearly eliminated the need to sell obsolete stocks on multiple days until portfolio size got much bigger ~ $500k This did improve overall returns Total Returns 1314% Benchmark 192.5% Max Drawdown 48.1% Alpha 0.88 Beta 0.89 Sharpe 3.17 Volatility 0.31 PF 2016_0807: Leverage 3: Allow the number of equities to increase with portfolio value Try context.holdings = max(10, int( portfolio_value/30e3 ) As expected this reduced volatility. It also had some benefit to overall return Total Returns 1356% Benchmark 192.5% Max Drawdown 48.5% Alpha 0.91 Beta 0.91 Sharpe 3.72 Volatility 0.28 PF 2016_0807: Drawdown protection (improved to acceptable level) Add a simple drawdown protection based on simple moving averages of SPY If SPY_SMA_fast < SPY_SMA_slow, then go to cash; else use the algorithm Fast period should be on the order of the shortest momentum filter (20 days) Since SMA filter is slower than EMA a period less than 20 days is desired. Slow period should be several multiples of the fast period, but not slower than the overall algo. The geometric average of the four periods (20,60,125,252) is 78 days A 15/80 day test provided good drawdown reduction (26% vs 48%) with about 10% loss in total return 15/80 Cash Total return 1204% Alpha 0.85 Sharpe 4.00 Max DD 26% PF 2016_0807: Most asset allocation models would exit to bonds vs cash, so that was tried as well Bond set = [TLT, IEF, AGG] 15/80 Bonds Total return 1790% Alpha 1.32 Sharpe 6.07 Max DD 20% This is a nice result. A somewhat better result might be had by allowing rotation between stocks, bonds, cash, or some combination of stocks/bonds, but that is beyond my current purpose. PF 2016_0807: What is effect of the ADDV limit? ADDV limit. $30k per holding and $100k initial investment. Exiting to bonds when indicated by 15/80 SMA test $0.2M: Total return 1810% Alpha 1.34 Sharpe 6.15 Max DD 20% $0.5M: Total return 1790% Alpha 1.32 Sharpe 6.07 Max DD 20% $1.5M: Total return 1546% Alpha 1.13 Sharpe 5.00 Max DD 20% PF 2016_0807: What is the effect of the efficiency threshold? I tried several values as shown below Any limit > 0.0 has a good result until some point above 0.5. Garner's 0.031 recommendation for his top 10 algorithm looks good. My finding is for a variable and larger set of equities (10 to 60 in any trial). PF 2016_0807: Intermediate is the return reported for week of 1/3/2010 (near midpoint) Limit 0.0 total return 1815% intermediate 848% Sharpe 6.15 Limit 0.031 total return 1790% intermediate 836% Sharpe 6.07 Limit 0.1 total return 1786% intermediate 818% Sharpe 6.05 Limit 0.2 total return 1784% intermediate 813% Sharpe 6.03 Limit 0.4 total return 1799% intermediate 791% Sharpe 6.09 Limit 0.5 total return 1764% intermediate 809% Sharpe 5.97 Limit 0.7 total return 1550% intermediate 739% Sharpe 5.20 ==> might as well use a limit of 0.0 ==> This is equivalent to stating factor_4 > 1.0 which is easier to implement. PF 2016_0809: Thomas Chang published a more compact implementation of the four factor ranking. I'll probably use this in a future version of this strategy PF 2016_0809: However problems remain Most notable there is a very large sensitivity to starting date Garner made several posts showing this wildly variable performance. Over the span of 1/4/2003 to 11/30/2015 the total return can be as little as 200% with no improvement in volatility or drawdown vs SP500 buy-and-hold. Starting date sensitivity is a common problem in asset rotation strategies, but this one is particularly sensitive. **PF 2016_0809: End of comments relative to the monthly version of the strategy **PF 2016_0814: Start of comments relative to the weekly version of the strategy PF 2016_0814: Potential remedies for rotation method and timing a) rebalance more frequently, perhaps weekly b) initiate multiple overlapping positions (invest weekly and and hold monthly) c) consider using a small cap proxy for the entry/exit test. PF 2016_0814: Potential remedies for asset selection a) investigate whether the some very simple fundamentals screening could reduce the likelihood of buying troublesome stocks b) investigate different momentum models (slope, percent below high) PF 2016_0814: Rebalance weekly Below are returns as a function of days_offset offset= 0 total return 719% Alpha 0.48 Sharpe 3.90 Max DD 47% Vol 0.28 offset= 1 total return 317% Alpha 0.17 Sharpe 0.73 Max DD 52% Vol 0.31 offset= 2 total return 1142% Alpha 0.48 Sharpe 3.14 Max DD 29% Vol 0.28 offset= 3 total return 1308% Alpha 0.94 Sharpe 3.72 Max DD 29% Vol 0.27 offset= 4 total return 1762% Alpha 1.29 Sharpe 5.35 Max DD 33% Vol 0.25 PF 2016_0814: Rebalance weekly and check that free cash flow is positive Rationale: a quick look of stocks selected during periods of poor algorithm performance showed poor fundamentals. Positive FCF might be one of the simplest tests for "minimally acceptable" fundamentals. See returns as a function of days_offset offset= 0 total return 1024% Alpha 0.79 Sharpe 3.29 Max DD 44% Vol 0.25 offset= 1 total return 1201% Alpha 0.86 Sharpe 3.70 Max DD 34% Vol 0.25 offset= 2 total return 1912% Alpha 1.41 Sharpe 6.16 Max DD 26% Vol 0.24 offset= 3 total return 1033% Alpha 0.73 Sharpe 3.20 Max DD 30% Vol 0.24 offset= 4 total return 1533% Alpha 1.12 Sharpe 5.03 Max DD 28% Vol 0.23 ==> This is more consistent with regard to return and volatility is improved somewhat, but max DD is still too high. PF 2016_0814: Effect of proxy choice Rationale: Strategy considers top 3000 stocks, so a broader proxy should be used using a middling SPY scenario (weekly with positive FCF and offset = 3 days) Here are results for some broad market candidates: SPY (SP500) total return 1033% Alpha 0.73 Sharpe 3.20 Max DD 30% Vol 0.24 IWV (Russell 3000) total return 1320% Alpha 0.95 Sharpe 4.13 Max DD 30% Vol 0.24 VTI (all cap) total return 1312% Alpha 0.95 Sharpe 4.10 Max DD 30% Vol 0.24 Unfortunately I can't find an equal weighted fund that from 2003. ==> As expected broad index (IWV or VTI) may be better PF 2016_0814: Revisiting liquidity I'm still encountering some liquidity problems. Several times per year a stock will take several days to sell the position. using a middling SPY scenario (weekly with positive FCF and offset = 3 days, no filtering for price_vs_max) Here are results for some pairings of ADDV periods and values 20d/500k total return 1033% Alpha 0.73 Sharpe 3.20 Max DD 30% Vol 0.24 60d/500k total return 1177% Alpha 0.84 Sharpe 3.69 Max DD 31% Vol 0.24 ==> use the 60 day test PF 2016_0814: Effect of limiting performance vs recent maximum Rationale: Some stocks may experience a very large recent spike in price then enter a period of decline. Although in decline the large price jump keeps the stock in our selection set. Implement a simple filter max_N = max close in past N days price_vs_max = close[0]/max_N if price_vs_max > threshold then stock is OK to use using a middling SPY scenario (weekly with positive FCF and offset = 3 days) Here are results for some pairings of N and threshold 20d/0.0 total return 1177% Alpha 0.84 Sharpe 3.69 Max DD 31% Vol 0.24 20d/0.85 total return 1116% Alpha 0.80 Sharpe 3.48 Max DD 26% Vol 0.24 20d/0.85 total return 1116% Alpha 0.80 Sharpe 3.48 Max DD 26% Vol 0.24 60d/0.7 total return 1182% Alpha 0.85 Sharpe 3.71 Max DD 28% Vol 0.24 60d/0.85 total return 1147% Alpha 0.82 Sharpe 3.59 Max DD 28% Vol 0.24 ==> This seems unlikely to be a beneficial test PF 2016_0814: Investors often chase the shiny object. Define augmented momentum to provide a bonus for the best single day in the period best = np.nanmax(np.diff(close,axis=0),axis=0) out[:] = (close[-1]/close[0]) + (best/close[0]) Augmented momentum for Factor_1, simple_momentum for Factor_2, 3, 4 offset= 0 total return 1102% Alpha 0.78 Sharpe 3.30 Max DD 47% Vol 0.25 offset= 1 total return 1167% Alpha 0.83 Sharpe 3.55 Max DD 36% Vol 0.25 offset= 2 total return 2565% Alpha 1.92 Sharpe 8.42 Max DD 25% Vol 0.23 offset= 3 total return 1247% Alpha 0.90 Sharpe 3.87 Max DD 28% Vol 0.24 offset= 4 total return 1481% Alpha 1.08 Sharpe 4.65 Max DD 31% Vol 0.24 Augmented momentum for Factor_1,2,3,4 offset= 0 total return 1392% Alpha 1.01 Sharpe 4.27 Max DD 44% Vol 0.25 offset= 2 total return 2756% Alpha 2.07 Sharpe 9.13 Max DD 29% Vol 0.23 offset= 3 total return 1702% Alpha 1.25 Sharpe 5.50 Max DD 28% Vol 0.24 ==> This is intersting, but using if feels like data fitting so I won't PF 2016_0814: Can we improve max drawdown by adjusting the stop loss parameter? Garner's original strategy used a 75% stop loss limit. This is probably a good value for monthly rebalance, but a tighter limit might make sense for weekly rebalancing Check this vs a middling scenario (weekly with positive FCF, offset = 0 days, no filtering for price_vs_max, simple_momentum model, 60 day ADDV > $500k) Here are results for various stop loss limits 0% total return 1263% Alpha 0.91 Sharpe 3.93 Max DD 31% Vol 0.24 60% total return 1231% Alpha 0.88 Sharpe 3.84 Max DD 31% Vol 0.24 75% total return 1177% Alpha 0.84 Sharpe 3.69 Max DD 31% Vol 0.24 80% total return 1095% Alpha 0.78 Sharpe 3.20 Max DD 30% Vol 0.24 85% total return 1036% Alpha 0.73 Sharpe 3.32 Max DD 30% Vol 0.24 90% total return 787% Alpha 0.55 Sharpe 2.56 Max DD 29% Vol 0.23 ==> This result suprises me. It must be that a significant fraction of the stocks that lose 25% during the week later recover some of this loss. PF 2016_0814: Weekly rebalance baseline Let's put together some of the apparently better ideas 1. Rebalance weekly vs monthly 2. Decide whether to be in stocks or bonds (safe) based on fast vs slow SMA of VTI (All cap index) 3. Only consider stocks that a) are in the top 3000 by market cap b) have net gain over the past 252 days c) have positive cash flow d) have and average daily dollar volume of at least $500k over the past 60 days 4. Select the top N of these stocks based on combined ranking over 20, 60, 125, 252 days 5. Set the value N to be portfolio value divided by $30k with a minimum of 10 stocks 6. Define a safe set of bonds to hold when not in stocks 7. Disable Garner's stop loss and profit taking as these don't benefit weekly strategy offset= 0 total return 1422% Alpha 1.03 Sharpe 4.34 Max DD 36% Vol 0.25 offset= 1 total return 1366% Alpha 0.98 Sharpe 4.17 Max DD 36% Vol 0.25 offset= 2 total return 2193% Alpha 1.63 Sharpe 6.96 Max DD 32% Vol 0.24 offset= 3 total return 1543% Alpha 1.13 Sharpe 4.81 Max DD 32% Vol 0.24 offset= 4 total return 1794% Alpha 1.32 Sharpe 5.65 Max DD 26% Vol 0.24 PF 2016_0814: Still have liquidity problems, especially with low share price stocks. Try filtering those Using the offset = 0d case above Price > $0 total return 1422% Alpha 1.03 Sharpe 4.34 Max DD 36% Vol 0.25 ??? Price > $3 total return 1343% Alpha 0.97 Sharpe 4.10 Max DD 36% Vol 0.25 Price > $5 total return 1069% Alpha 0.75 Sharpe 3.22 Max DD 33% Vol 0.25 The progress from $0 to $3 to $5 did reduce the number of partial order messages, but also degraded returns for the case of days_offset=0. Checking the result for all five day_offset cases and Price > $3: offset= 0 total return 1343% Alpha 0.97 Sharpe 4.10 Max DD 36% Vol 0.25 offset =1 total return 1368% Alpha 0.99 Sharpe 4.17 Max DD 36% Vol 0.25 offset= 2 total return 2292% Alpha 1.71 Sharpe 7.36 Max DD 32% Vol 0.24 offset= 3 total return 1514% Alpha 1.10 Sharpe 4.72 Max DD 32% Vol 0.24 offset= 4 total return 1609% Alpha 1.17 Sharpe 5.03 Max DD 28% Vol 0.24 ==> This slight overall reduction is OK, but I'll continue to investigate liquidity fixes. **PF: End of comments relative to the weekly version of the strategy **PF: Parking lot of things to check later. List in no particular order. - how to reduce drawdown spans (strategy can result in ~3y periods with no net gain) - how to avoid occasional liquidity (partial order) problems - evaluating possibility of nonuniform weighting - implementing overlapping holding periods (maybe order every 2 days and hold for 10) - eliminating use of the built-in market_cap() method that is not supported in live trading - evaluating results in a tear sheet - evaluating results with the AlphaLens tool - how to safely use leverage > 1.0 (see Guy Fleury posts) - picking a better safe set (little thought went into this one) - checking momentum of each safe asset before purchase (... in or cash for each) - exploring alternative entry/exit logic (vs the simple fast/slow SMA) **PF: that is the parking lot for now """ # # import methods and data # from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline import CustomFactor from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.data import morningstar from quantopian.pipeline.factors import AverageDollarVolume import numpy as np from collections import defaultdict # # define custom classes # class simple_momentum(CustomFactor): inputs = [USEquityPricing.close] window_length = 1 def compute(self, today, assets, out, close): out[:] = close[-1]/close[0] class augmented_momentum(CustomFactor): inputs = [USEquityPricing.close] window_length = 1 def compute(self, today, assets, out, close): best = np.nanmax(np.diff(close,axis=0),axis=0) out[:] = (close[-1]/close[0]) + (best/close[0]) class price_vs_max(CustomFactor): inputs = [USEquityPricing.close] window_length = 252 def compute(self, today, assets, out, close): out[:] = close[-1]/np.nanmax(close, axis=0) class market_cap(CustomFactor): inputs = [USEquityPricing.close, morningstar.valuation.shares_outstanding] window_length = 1 def compute(self, today, assets, out, close, shares): out[:] = close[-1] * shares[-1] class get_fcf_per_share(CustomFactor): inputs = [morningstar.valuation_ratios.fcf_per_share] window_length = 1 def compute(self, today, assets, out, fcf_per_share): out[:] = fcf_per_share class get_last_close(CustomFactor): inputs = [USEquityPricing.close] window_length = 1 def compute(self, today, assets, out, close): out[:] = close[-1] def initialize(context): # # schedule methods # schedule_function(func=periodic_rebalance, date_rule=date_rules.week_start(days_offset=1), time_rule=time_rules.market_open(), half_days=True) schedule_function(func=daily_rebalance, date_rule=date_rules.every_day(), time_rule=time_rules.market_close(hours=1)) # # set portfolis parameters # set_do_not_order_list(security_lists.leveraged_etf_list) context.acc_leverage = 1.00 context.min_holdings = 10 # # set profit taking and stop loss parameters # context.profit_taking_factor = 0.01 context.profit_taking_target = 10.0 #set much larger than 1.0 to disable context.profit_target={} context.profit_taken={} context.stop_pct = 0.0 # set to 0.0 to disable context.stop_price = defaultdict(lambda:0) # # Set commission model to be used # set_commission(commission.PerShare(cost=0.005, min_trade_cost=1.00)) # # Define safe set (of bonds) # context.safe = [ sid(23870), #IEF sid(23921), #TLT sid(25485) #AGG ] # # Define proxy to be used as proxy for overall stock behavior # set default position to be in safe set (context.buy_stocks = False) # context.canary = sid(22739) context.buy_stocks = False # # Establish pipeline # pipe = Pipeline() attach_pipeline(pipe, 'ranked_stocks') # # Define the four momentum factors used in ranking stocks # factor1 = simple_momentum(window_length=20) pipe.add(factor1, 'factor_1') factor2 = simple_momentum(window_length=60) pipe.add(factor2, 'factor_2') factor3 = simple_momentum(window_length=125) pipe.add(factor3, 'factor_3') factor4 = simple_momentum(window_length=252) pipe.add(factor4, 'factor_4') # # Define other factors that may be used in stock screening # factor5 = get_fcf_per_share() pipe.add(factor5, 'factor_5') factor6 = AverageDollarVolume(window_length=60) pipe.add(factor6, 'factor_6') factor7 = get_last_close() pipe.add(factor7, 'factor_7') factor_4_filter = factor4 > 1.0 # only consider stocks with positive 1y growth factor_5_filter = factor5 > 0.0 # only consider stocks with positive FCF factor_6_filter = factor6 > 0.5e6 # only consider stocks trading >$500k per day # factor_7_filter = factor7 > 3.00 # only consider stocks that close above this value # # Establish screen used to establish candidate stock list # mkt_screen = market_cap() stocks = mkt_screen.top(3000) total_filter = (stocks & factor_4_filter & factor_5_filter & factor_6_filter) pipe.set_screen(total_filter) # # Establish ranked stock list # factor1_rank = factor1.rank(mask=total_filter, ascending=False) pipe.add(factor1_rank, 'f1_rank') factor2_rank = factor2.rank(mask=total_filter, ascending=False) pipe.add(factor2_rank, 'f2_rank') factor3_rank = factor3.rank(mask=total_filter, ascending=False) pipe.add(factor3_rank, 'f3_rank') factor4_rank = factor4.rank(mask=total_filter, ascending=False) pipe.add(factor4_rank, 'f4_rank') combo_raw = (factor1_rank+factor2_rank+factor3_rank+factor4_rank)/4 pipe.add(combo_raw, 'combo_raw') pipe.add(combo_raw.rank(mask=total_filter), 'combo_rank') def before_trading_start(context, data): # # Calculate maximum number of stocks to buy # n_30 = int(context.portfolio.portfolio_value/30e3) context.holdings = max(context.min_holdings, n_30) # # Screen to find the current top stocks # context.output = pipeline_output('ranked_stocks') ranked_stocks = context.output context.stock_factors = ranked_stocks.sort(['combo_rank'], ascending=True).iloc[:context.holdings] context.stock_list = context.stock_factors.index # # Use fast/slow SMA test of proxy to determine whether to be in stocks vs safe # Canary = data.history(context.canary, 'price', 80, '1d') Canary_fast = Canary[-15:].mean() Canary_slow = Canary.mean() context.buy_stocks = False if Canary_fast > Canary_slow: context.buy_stocks = True def daily_rebalance(context, data): # # Do daily maintenance # a) sell obsolete positions # b) implement stop loss # c) implement profit taking # d) record values for backtest display # # # Sell any holdings that are not in context.this_periods_list # for stock in context.portfolio.positions: if data.can_trade(stock): if stock not in context.this_periods_list: order_target(stock, 0) # # update stop loss limits and sell any stocks that are below their limits # for stock in context.portfolio.positions: if data.can_trade(stock): price = data.current(stock, 'price') context.stop_price[stock] = max(context.stop_price[stock], context.stop_pct * price) if price < context.stop_price[stock]: order_target(stock, 0) context.stop_price[stock] = 0 log.info("%s stop loss"%stock) # # Profit take if profit target is met # Skip this for safe set assets # takes = 0 for stock in context.portfolio.positions: if stock not in context.safe: if data.can_trade(stock) and data.current(stock, 'close') > context.profit_target[stock]: context.profit_target[stock] = data.current(stock, 'close')*1.25 profit_taking_amount = context.portfolio.positions[stock].amount * context.profit_taking_factor takes += 1 log.info(profit_taking_amount) order_target(stock, profit_taking_amount) # # Record parameters # n100 = len(context.output)/100 record(leverage=context.account.leverage, positions=len(context.portfolio.positions), t=takes, candidates=n100) def periodic_rebalance(context,data): # # rebalance portfolio based on most recent context.buy_stocks signal # # rebalance portfolio in stocks # if context.buy_stocks: context.this_periods_list = context.stock_list # # sell any holdings not in this period's stock list # for stock in context.portfolio.positions: if data.can_trade(stock): if stock not in context.this_periods_list: order_target(stock, 0) # # equally weight portfolio over assets that can trade # don't buy stock if its 20d momentum (Factor_1) is not positive # set profit_target threshold based on recent close # weight = context.acc_leverage / len(context.stock_list) p_tgt = context.profit_taking_target for stock in context.stock_list: if stock in security_lists.leveraged_etf_list: continue if data.can_trade(stock) and context.stock_factors.factor_1[stock] > 1: order_target_percent(stock, weight) context.profit_target[stock] = data.current(stock, 'close')*p_tgt # # otherwise put portfolio into safe set # else: context.this_periods_list = context.safe # # sell any holdings not in safe set # for stock in context.portfolio.positions: if data.can_trade(stock): if stock not in context.safe: order_target(stock, 0) # # equally weight portfolio over safe assets that can trade # n = 0 for stock in context.safe: if data.can_trade(stock): n += 1 if n > 0: weight = 1.0/n for stock in context.safe: if data.can_trade(stock): order_target_percent(stock, weight)