Back to Community
2013.12.31-2019.7.31 intraday momentum

the backtesting of spy about intraday momentum:

My investment hypothesis is based upon the paper “market intraday momentum” from Lei Gao, Yufeng Han, Sophia Zhengzi Li and Guofu Zhou (2017 version), which documented the intraday momentum pattern: the first half hour return on the market since the previous day’s market close predicts the last half-hour return; the predictability, both statistically and economically significant, is stronger on more volatile days, on higher volume days, on recession days, and on major macroeconomic news release days. The paper presented that the first half hour return and the second to last half hour return are independent and complementary in forecasting the last half-hour return.
However, from the regression model I did in the Quantopian notebook. The first half hour return is statistically insignificant. So I change my backtesting model’s investment hypothesis from “if the 1st and second to last return are both positive, then buy the stock 35 minutes before the market close and sell it 5 minutes before the market close;
if the 1st and second to last return are both negative, then short sell the stock 35 minutes before the market close and buy it back 5 minutes before the market close.” to “if the second to last return is positive, then buy the stock 35 minutes before the market close and sell it 5 minutes before the market close; vice versa.” The result seems much better than the previous hypothesis.
The normal trading hours in the United States begin at 9:30 am and end at 4pm. I do the regression from 2002-1-2 to 2013-12-31 because the Quantopian can only start from 2002. The paper’s time span is from 1993-2-1 to 2013-12-31 because the TAQ data from WRDS is available only until December 2014 as of June 2017. The tradable SPY, the most actively traded ETF that tracks the S&P 500 is used for trading for simplicity. For the backtesting, I did it for two periods: from 2002.1.2 to 2013.12.31 and from 2013.12.31 to 2019.7.31.

Clone Algorithm
4
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
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
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
import quantopian.optimize as opt
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.experimental import risk_loading_pipeline

import math
import numpy as np

def initialize(context):
    context.spy = sid(8554)  
    
 # Start function 5 mins before close, do calculations, send orders.
    algo.schedule_function(
        close,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=5)
    )

 # Start function 35 mins before close, do calculations, send orders.
    algo.schedule_function(
        open,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=35),
    )

def handle_data(context, data):
    """
    """
    pass

def open(context, data):
    first_30m_ret, second_to_last_30m_ret = get_30m_para(context, data)
    if (first_30m_ret > 0) & (second_to_last_30m_ret > 0):
        order_target_percent(context.spy, 1)
    elif (first_30m_ret < 0) & (second_to_last_30m_ret < 0):
        order_target_percent(context.spy, -1)
    else:
        pass

def close(context, data):
  order_target_percent(context.spy, 0)

def get_30m_para(context, data):
    prices = data.history(context.spy, 'price', 30, '1m')
    first_30m_ret = (prices[1] - prices[0]) / prices[0]
    second_to_last_30m_ret = (prices[-2] - prices[-3]) / prices[-3]
    return first_30m_ret,second_to_last_30m_ret
There was a runtime error.
12 responses

Has anyone considered backtesting market making intra day? Assuming some sort of random capture of a portion of an assumed bid / offered spread?

Has anyone considered backtesting market making intra day? Assuming some sort of random capture of a portion of an assumed bid / offered spread?

As a retail trader (or even via a Prime Broker) it’d be virtually to impossible to compete in that space in the live market, without ultra low latency infrastructure, market data and trading and risk layer in exchange colo racks, trading on one’s own execution membership, MM license, etc, so I don’t see the point. The Citadels of the world will kill you.

Perhaps I should have added that I have been looking at Crypto and Dexbot.

And dex not cex. On bitshares the fees are tiny and the spreads wide. Low latency is not a factor. A delay is built in on the e bitshares blockchain. Its seconds not nano seconds.

@Zenothestoic How would you backtest a market making strategy? I'm assuming you would need tick data for quotes and trades. Putting those together you could figure out which trades happened at the bid and which happend at the ask. Then you could assume that if you had an order at the top of the book, a taker trade that crosses it would have given you a fill. This still doesn't factor in the sub-pennying you'd be a victim of (on equities exchanges) or other ways the market would react to your limit order, some to your benefit, and some to your dismay. If your hair isn't already grey, this seems like a good way to accomplish this. I've heard of retail algo traders successfully market making on crypto, and they claimed you just need to be the fastest.

@Meiqing Gu The losses incurred by your backtest come from the slippage model. If you try your strategy with slippage and commissions turned off you can assess whether there is any alpha at all in the strategy. There is not. It is mostly flat.

Here's what it looks like without slippage.

Clone Algorithm
1
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
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
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
import quantopian.optimize as opt
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.experimental import risk_loading_pipeline

import math
import numpy as np

def initialize(context):
    set_slippage(slippage.FixedSlippage(spread=0))
    set_commission(commission.PerTrade(cost=0))
    
    context.spy = sid(8554)  
    
 # Start function 5 mins before close, do calculations, send orders.
    algo.schedule_function(
        close,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=5)
    )

 # Start function 35 mins before close, do calculations, send orders.
    algo.schedule_function(
        open,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=35),
    )

def handle_data(context, data):
    """
    """
    pass

def open(context, data):
    first_30m_ret, second_to_last_30m_ret = get_30m_para(context, data)
    if (first_30m_ret > 0) & (second_to_last_30m_ret > 0):
        order_target_percent(context.spy, 1)
    elif (first_30m_ret < 0) & (second_to_last_30m_ret < 0):
        order_target_percent(context.spy, -1)
    else:
        pass

def close(context, data):
  order_target_percent(context.spy, 0)

def get_30m_para(context, data):
    prices = data.history(context.spy, 'price', 30, '1m')
    first_30m_ret = (prices[1] - prices[0]) / prices[0]
    second_to_last_30m_ret = (prices[-2] - prices[-3]) / prices[-3]
    return first_30m_ret,second_to_last_30m_ret
There was a runtime error.

Seems most of the gains come during high volatility market regimes, while the losses come during low volatility, which is the opposite of what I'd expect from a momentum strategy. So that's interesting.

Loading notebook preview...
Notebook previews are currently unavailable.

@Viridian Hawk You helped me a lot, thank you so much.

@Zenothestoic How would you backtest a market making strategy?
Exactly as you suggest. And just as with slippage here on Q, you have to make some assumptions as to when your bids and offers get hit (or missed). Using random switches. I'm not yet convinced Dexbot has it right. Partly there seem to be technical difficulties connecting to a node on the bitshares network, partly because I'm not sure that enough thought has been given to the MM systems they are using. So far, they have done no back testing at all. I have convinced them that this is necessary and I believe they are putting something together (a) using synthesized order book data and (b) by recreating the state of the order book since inception of some pairs back to around 2014. Perhaps using minute data would suffice.

Apart from more traditional market-making they have also designed a grid trading strategy. That at least is easy enough to back test, again making some assumptions about slippage.

The big question is how the likes of Rentec make their money on conventional markets. And then to try and ape some of these methods on crypto. I'm not at all sure the extent to which "dirty" tricks are necessary. Not illegal tricks but just stuff like seeing orders before they are actually confirmed on the block-chain.

Frankly I have no real idea how the HFT guys make their money. But it would be good to copy some of their ideas.

@Zenothestoic Well, MM...
1) earn the spread by providing liquidity on both sides.
2) They have relationships with the exchanges whereby special orders types and access have been developed to give them an advantage over all the other market participants. For example, on equities HFT MM can sub-penny (offer $0.0001 better than the best quote) in order to jump to the front of the line, thereby earning the spread while taking negligible risk.
3) They post very small volume on their bids and asks so that large orders will have to eat through the book, earning the HFT MM wider spreads.
4) They also use low-latency connections between exchanges to watch large orders come in at one exchange and so they can buy up all the liquidity at the other exchanges before the order makes it there. That way they flip the stock with basically no risk (besides the extreme cost of the low latency infrastructure).
5) They also employ various methods such as buying retail order flow so that they can take the opposite side of trades from dumb-money while avoiding making market for smart money. That way they don't get run over. My impression is that detecting whales is an important part of MM.

@Meiqing Gu No problem. I think I read the same paper as you. I had a little more luck recreating it with positive returns, but my conclusion was that it wasn't strong enough to be worth trading. Also it has been a consistent loser since the paper was published, which indicates either the authors of the paper inadvertently overfit to historical data or the trade simply became too crowded since then.

Fascinating. It has long been my impression that HFT is "corrupt". The whole world seems to be a corrupt, dog eat dog, carnivorous, Darwinian environment. no wonder we are in such a mess. Instead of "love thy neighbor", the commandment we live by is "shaft thy neighbor".