This thread is meant to continue the development of the In & Out strategy started on Quantopian. The first challenge for us will probalbly be to translate our ideas to QC code.
I'll start by attaching the version Bob Bob kindly translated on Vladimir's request.
Vladimir:
About your key error, did you also initialize UUP like this?
self.UUP = self.AddEquity('UUP', res).Symbol
Kyle K Oates
Quantopian forum archive as pdf:
https://drive.google.com/file/d/1JE-2Ter1TWuQvZC12vC892c2wLPHAcUS/viewTentor Testivis
Hey Kyle, good idea - thanks!
It's also archived on The WayBackMachine
Jack Pizza
Hey Guys, did anybody submit this to QC? so they can help port Vladimi's latest version, and we can pick up from there. Let me know if someone submits it or I'll do it, then we can post the QC version here, but probably maybe Vlad should do it incase the support team has questions about specific functions.
Chris Liu
@Tentor Testivis
Kudoz!
Chris Liu
I backtested this algo at my local PC too, the result is not as good as Quantopian too.
Jack Pizza
Chris Liu Quantopian backtest results have always been suspect, since the very beginning and i doubt they fixed anything while cutting everything down.
As I mentioned elsewhere and even on Quantopian's forums years ago, found multiple days with wrong historical data in zipline production servers, which they said they don't go back and correct.
Tentor Testivis
I've implemented the last version I cloned from Quantopian, I belive Peter posted it.
The original code:
"""
Based on 'In & Out' strategy by Peter Guenther 10-04-2020
expanded/inspired by Tentor Testivis, Dan Whitnable (Quantopian), Vladimir, and Thomas Chang.
https://www.quantopian.com/posts/new-strategy-in-and-out
"""
# Import packages
import numpy as np
import pandas as pd
import scipy as sc
def initialize(context):
# Feed-in constants
context.INI_WAIT_DAYS = 15 # out for 3 trading weeks
# 'In' and 'out' holdings incl. weights
context.HLD_IN = {symbol('SPY'): 1.0}
context.HLD_OUT = {symbol('TLT'): .5, symbol('IEF'): .5}
# Market and list of signals based on ETFs
context.MRKT = symbol('SPY')
context.PRDC = symbol('XLI') # production (industrials)
context.METL = symbol('DBB') # input prices (metals)
context.NRES = symbol('IGE') # input prices (natural res)
context.DEBT = symbol('SHY') # cost of debt (bond yield)
context.USDX = symbol('UUP') # safe haven (USD)
context.SIGNALS = [context.PRDC, context.METL, context.NRES, context.DEBT, context.USDX]
# Pairs for comparative returns signals
context.GOLD = symbol('GLD') # gold
context.SLVA = symbol('SLV') # VS silver
context.UTIL = symbol('XLU') # utilities
context.INDU = context.PRDC # vs industrials
context.SHCU = symbol('FXF') # safe haven (CHF)
context.RICU = symbol('FXA') # risk currency (AUD)
context.FORPAIRS = [context.GOLD, context.SLVA, context.UTIL, context.SHCU, context.RICU]
# Initialize variables
## 'In'/'out' indicator
context.be_in = 1
## Day count variables
context.dcount = 0 # count of total days since start
context.outday = 0 # dcount when context.be_in=0
## Flexi wait days
context.WDadjvar = context.INI_WAIT_DAYS
# Commission
set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1.00))
# Schedule functions
schedule_function(
# daily rebalance if OUT of the market
rebalance_when_out_of_the_market,
date_rules.every_day(),
time_rules.market_open(minutes = 75)
)
schedule_function(
# weekly rebalance if IN the market
rebalance_when_in_the_market,
date_rules.week_start(days_offset=4),
time_rules.market_open(minutes = 75)
)
def rebalance_when_out_of_the_market(context, data):
# Returns sample to detect extreme observations
hist = data.history(context.SIGNALS+[context.MRKT]+context.FORPAIRS, 'close', 253, '1d').iloc[:-1]
hist_shift = hist.apply(lambda x: (x.shift(65)+x.shift(64)+x.shift(63)+x.shift(62)+x.shift(61)+x.shift(60)+x.shift(59)+x.shift(58)+x.shift(57)+x.shift(56)+x.shift(55))/11)
returns_sample = (hist/hist_shift-1)
# Reverse code USDX: sort largest changes to bottom
returns_sample[context.USDX] = returns_sample[context.USDX]*(-1)
# For pairs, take returns differential, reverse coded
returns_sample['G_S'] = -(returns_sample[context.GOLD] - returns_sample[context.SLVA])
returns_sample['U_I'] = -(returns_sample[context.UTIL] - returns_sample[context.INDU])
returns_sample['C_A'] = -(returns_sample[context.SHCU] - returns_sample[context.RICU])
context.pairlist = ['G_S', 'U_I', 'C_A']
# Extreme observations; statist. significance = 1%
pctl_b = np.nanpercentile(returns_sample, 1, axis=0)
extreme_b = returns_sample.iloc[-1] < pctl_b
# Determine waitdays empirically via safe haven excess returns, 50% decay
context.WDadjvar = int(max(0.50*context.WDadjvar, context.INI_WAIT_DAYS * max(1,returns_sample[context.GOLD].iloc[-1] / returns_sample[context.SLVA].iloc[-1],returns_sample[context.UTIL].iloc[-1] / returns_sample[context.INDU].iloc[-1],returns_sample[context.SHCU].iloc[-1] / returns_sample[context.RICU].iloc[-1])))
adjwaitdays = min(60, context.WDadjvar)
# Determine whether 'in' or 'out' of the market
if (extreme_b[context.SIGNALS+context.pairlist]).any():
context.be_in = False
context.outday = context.dcount
if context.dcount >= context.outday + adjwaitdays:
context.be_in = True
context.dcount += 1
# Swap to 'out' assets if applicable
if not context.be_in:
for asset, weight in context.HLD_OUT.items():
order_target_percent(asset, weight)
for asset in context.portfolio.positions:
# Close 'In' holdings
if asset not in context.HLD_OUT:
order_target_percent(asset, 0)
# Record
record(in_market=context.be_in, num_out_signals=extreme_b[context.SIGNALS+context.pairlist].sum(), waitdays=adjwaitdays)
def rebalance_when_in_the_market(context, data):
# Swap to 'in' assets if applicable
if context.be_in:
for asset, weight in context.HLD_IN.items():
order_target_percent(asset, weight)
for asset in context.portfolio.positions:
# Close 'Out' holdings
if asset not in context.HLD_IN:
order_target_percent(asset, 0)
I don't remember the exact values for returns, DD and so on but I think the QC implementation underperforms the Q version.
I also added some plots. Btw., does any one know how to get rid of the unnecessary ones (alpha, insight...)? I disabled them in the backtest results but that doesn't seem to matter.
Derek Melchin
Hi Tentor,
Thanks for sharing the algorithm above. Since we are placing trades 140 minutes after the market open, we should ensure we set the data resolution to minute so that we avoid stale fill prices. Additionally, the algorithm makes 2 History calls every time the `daily_check` method executes. We typically suggest avoid this practice. In the backtest attached below, I've changed the data resolution to the minute-level and I've moved the first History method into Initialize to speed up the execution. Since the length of the second history call is dynamic, I've left this one to be implemented.
For those who are new to the platform, I recommend reviewing our documentation on RollingWindows, warm-up periods, and consolidators. All of these concepts are utilized in the attached algorithm.
In regards to the difference in performance from our platform and Quantopian, we can contribute this to the difference in fee models and the fact that we simulate with L1 data.
Best,
Derek Melchin
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Tentor Testivis
Thanks Derek!
Warmup, consolidating and all that stuff is still a little confusing to me. However, setting Resolution.Minute gave the algo from my previous post a nice boost in performance.
Now I'm off for the weekend.
You all have a great one!
Jack Pizza
This is Vlad's Version, I'll try to convert later if someone doesn't by then.
# Intersection of ROC comparison using OUT_DAY approach
import numpy as np
# ------------------------------------------------------------------------------------------------------
STOCKS = symbols('QQQ'); BONDS = symbols('TLT','IEF'); LEV = 1.00; wt = {};
SLV = symbol('SLV'); GLD = symbol('GLD'); XLI = symbol('XLI'); XLU = symbol('XLU'); SHY = symbol('SHY');
MKT = symbol('QQQ'); VOLA = 126; BULL = 1; COUNT = 0; OUT_DAY = 0; BASE_RET = 85;
# ------------------------------------------------------------------------------------------------------
def initialize(context):
schedule_function(trade, date_rules.every_day(), time_rules.market_open(minutes = 140))
schedule_function(record_vars, date_rules.every_day(), time_rules.market_close())
def trade(context,data):
global BULL, COUNT, OUT_DAY
vola = data.history(MKT, 'price', VOLA + 1, '1d').pct_change().std() * np.sqrt(252)
WAIT_DAYS = int(vola * BASE_RET)
RET = int((1.0 - vola) * BASE_RET)
prices = data.history([SLV, GLD, XLI, XLU, SHY], 'price', RET + 2, '1d').iloc[:-1].dropna()
r = prices.pct_change(RET).iloc[-1]
exit = r[SLV] < r[GLD] and r[XLI] < r[XLU] and r[XLI] < r[SHY]
if exit: BULL = 0; OUT_DAY = COUNT;
elif (COUNT >= OUT_DAY + WAIT_DAYS): BULL = 1
COUNT += 1
wt_stk = LEV if BULL else 0;
wt_bnd = 0 if BULL else LEV;
for sec in STOCKS: wt[sec] = wt_stk / len(STOCKS);
for sec in BONDS: wt[sec] = wt_bnd / len(BONDS)
for sec, weight in wt.items():
cond1 = sec in context.portfolio.positions and weight == 0
cond2 = (sec not in context.portfolio.positions) and (weight > 0)
if cond1 or cond2:
order_target_percent(sec, weight)
record( wt_bnd = wt_bnd, wt_stk = wt_stk)
def record_vars(context, data):
record(leverage = context.account.leverage)
'''
Based on:
Peter Guenther's OUT_DAY approach.
Vladimir's "Price relative ratios (intersection) with wait days".
Thomas Chang changed BASE_RET parameter and reduced number of "unnecessary" trades.
Dan Whitnable added another pair and changed Price relative ratios (intersection) with intersection of ROC comparison what removed one more constant.
Many a little makes a mickle.
'''
Vladimir
@Tentor Testivis,
Here are my comments on Peter Guenther's "In & Out with sampled percentiles, return comparisons, and
relative ratios" algo which you just ported to QC.
Vladimir
2020-10-27
@Peter Guenther,
*In & Out with sampled percentiles, return comparisons, and relative ratios.*
Very interesting way of calculating adaptive momentum.
Very good performance metrics.
In my opinion, the range of *context.adjwaitdays* is too wide (15-80000), is this by design?
Look at Custom Data in attached backtest.
Vladimir
2020-10-28
@Peter Guenther,
I have manually calculated *context.adjvar* for GOLD and SILVER ratio of returns to show inconsistency of results with slight movement of SILVER price.
```
context.INI_WAIT_DAYS * max(1,returns_sample[context.GOLD].iloc[-1] / returns_sample[context.SLVA].iloc[-1]
```
returns_sample[context.GOLD].iloc[-1] = 10%
returns_sample[context.SLVA].iloc[-1] = 0.1% --> context.INI_WAIT_DAYS* 100 --> 15*max(1,100) = 1500
returns_sample[context.SLVA].iloc[-1] = 0.0% --> context.INI_WAIT_DAYS* inf - -> 15*max(1,inf) = inf
returns_sample[context.SLVA].iloc[-1] = -0.1% --> context.INI_WAIT_DAYS* -100 --> 15*max(1,-100) = 15
Peter Guenther
@all: Fantastic to see so many familiar faces here :) I finally made the transition as well ... slow mover.
@Tentor: I big thank you for starting this thread, so that the discussion can go on! Also, incredible performance there in terms of recoding the algo and thereby quickly making it available for further scrutiny and improvement!
@Kyle: Also a big thank you to you for pushing folks early to move this discussion to QC and for providing the discussion archive from Quantopian, much appreciated!
Derek Melchin: Thank you for the warm welcome to QuantConnect and your input regarding efficient coding! If you would be happy to, and time allows, it would be amazing if you could keep and eye on this thread; your input will be highly appreciated! This thread started on Quantopian only a few weeks ago and was one of the fastest growing discussions there. We hope that this thread will also be of great interest to the QC community and that members will join in to further scrutinize and improve the in & out algos discussed here.
@all: I will post an Intro to this thread which is intended to help new members joining the discussion to get a quick overview regarding what this thread is about (some key logics and findings from the archive). If you could help to vote this Intro up so that it gives people an entry point that would be highly appreciated. Maybe we can try to continuously keep at the top (by voting): Tentor's welcome message, the Intro, and Kyle's archive as a detailed backdrop.
Peter Guenther
(As a quick overview for new member regarding what this thread is about; please continuously vote this intro up so that it provides an entry point)
Intro to the In & Out discussion
Intuitively, it might be possible to generate excess returns via cleverly timed entries and exits in and out of the equity market. The ‘in & out’-type algos discussed here may be first steps toward developing a strategy that derives optimal moves in an out of the market on the basis of early indicators of equity market downturns. At the very least, the algos could start an interesting discussion regarding the sense and nonsense of trying to time market entry and exit in this way. Either way, your contribution is highly appreciated.
Backdrop of ‘in & out’-type algos
Looming stress in the equity market may show early in depressed prices of assets that are upstream in the value chain, e.g. resources and industrial goods. Stress may also show in increasing values of safe haven assets such as gold or USD. ‘In & out’-type algos tend to draw on these economic and sentiment indicators, e.g. via corresponding ETFs, to decide whether to be ‘in’ or ‘out’ of the equity market.
Suggested typical algo set up
1. In: The SPY is traded. This is so that the value of moving ‘in’ and ‘out’ of the market can be assessed versus the scenario of always being ‘in’.
2. Out: The money is usually invested in bonds (IEF and TLT). What we realized in our earlier discussions is that these alternative assets critically contribute to the strategy’s overall return (‘riding the jitter wave’). However, we also found that the ‘out’ indicator is not sufficiently precise, i.e. it should not be used directly to go short on the market.
Broader relevance: Amazing returns = superior stock selection strategy + superior in & out strategy
The assumption is that you will be able to plug your equity selection logic into this algo and get an additional boost in terms of your strategy’s returns. Yet, the equity selection component is not the main focus of this thread. Instead, the in & out component is which we believe tends to be underutilized in many algos.
Peter Guenther
And one to share, I reckon to celebrate the buzz that we have created with this thread on Quantopian. Here is from the members' closing remarks regarding Quantopian shutting down:
"... I saw a really amazing Algo that was posted very recently that used ETFs to find bad signals in the market so the investor could get out. It seemed to work fantastically. That algo seemed be incredibly amazing. The market good/bad signals are what mattered. What stocks you choose, as long as they were reasonable, didn't seem to matter that much. I have to wonder if they saw that and now they are off doing that and no longer needed us. Probably not but it makes you wonder." (Greg Kendall on 29 Oct 2020)
Adam W
Neat idea. Have you tried playing around with equity derivatives (futures/options on SPY) to improve the precision of the "Out" indicator? Those markets tend to be pretty responsive to changes in sentiment, or perhaps some NLP on market news (there's a Tiingo News data feed here)
Vladimir
Peter Guenther,
Welcome back to discussion.
Thomas Chang
Someone know how to translate the following code to QuantConnect? I use these in Quantopian to reduce the lot of "unnecessary" trades.
...
for sec in STOCKS: wt[sec] = wt_stk / len(STOCKS);
for sec in BONDS: wt[sec] = wt_bnd / len(BONDS)
for sec, weight in wt.items():
cond1 = sec in context.portfolio.positions and weight == 0
cond2 = (sec not in context.portfolio.positions) and (weight > 0)
if cond1 or cond2:
order_target_percent(sec, weight)
...
Peter Guenther
@Adam: Excellent thinking. I know that Tentor has started working on a 'futures edition' and it worked like a charm in terms of further improved total returns (at least for one of the early versions of the In & Out algo). It was particularly beneficial to integrate a clean 'copper signal' which can be an important indicator of China's production. The copper signal is included in the DBB ETF, but somewhat muddied due to the other two metals that the ETF tracks (aluminum and zinc).
Great hint there regarding derivatives and Tiingo News, these will definitely be interesting to explore!
@Validimir: will be good! Also thanks for the important points above, I reckon it will take me some time to actively look into them since I have to get my head around the coding here on QC first. Regarding the ratio examples: very true, I reckon that's the problem with using return ratios directly (small values and negative values). I will definitely give it a try regarding alternative operationalizations. I am wondering whether the actual indicator is that the alternative asset (e.g., silver) approaches a zero return while the other asset (e.g., gold) is still positive.
Thomas Chang
These will reduce a lot of "unnecessary" trades:
for sec, weight in wt.items():
cond1 = (self.Portfolio[sec].Quantity > 0) and (weight == 0)
cond2 = (self.Portfolio[sec].Quantity == 0) and (weight > 0)
if cond1 or cond2:
self.SetHoldings(sec, weight)
Tentor Testivis
Vladimir and Peter:
Nice to see you here and also thanks for providing some context!
I would like to quote Peter's introduction in my first post but I can't seem to edit it any more.
Thomas:
Thanks for finding the portfolio object and sharing the code - and of course nice to see you here!
Adam:
About using futures, I saw in the docs that futures data here consists of the individual contracts. Du you know how I would go about creating a continuous daily time series with them? My implementation was with the quantiacs toolbox where the futures are already continuous and Quantopian had a module called continuous_futures. Is there some equivalent here or do I need to stitch them together manually? The latter sounds a little tedious ;)
I agree with Peter about the NLP approach, very interesting! Will definitely look into it but it might take a while.
Allthough I've been a member here for some time (about a year) I was never very active and a little suprised to see that apparently I've already run over 1000 Backtests (I guess most of them were just clones). Anyway, I'll also need some time getting used to the workflow here...
Tentor Testivis
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!