MACD With Triple Screen

I’ve done backtest on a model that references to ideas from both Elder’s triple screen and MTS’ MACD.

Daily Bar Dimension:

  • DEA>0 (there’re ideas for daily bar trend filter, e.g. DEA+HISTOROC, DEA+HISTO)

Hourly Bar Dimension:

  • price closes above trend
  • trend moves up
  • dea>0

Exit Filter:

  • dea<0 and histo<0
  • or, price hits recent lows.
  • or, price breaks under trend.

Entry Signal:

histo turns from negative to position to BUY.

Backtest period from Mar2019 to Mar2023, with result as following:

Three types (deav1, deav2, dearc) of Daily Bar Dimension filters are tested, and performance measured based on Max Drawdown (MDD), Calmar, Sharpe ratio and winning ratio.

Two observations below:

  1. Trend filter at daily dimension does not impact the performance much and the winning ratio is about the same.

  2. Entry with 1sigma/2sigma at execution dimension matters a lot. Intuitively, entry signals at 2sigma will be much more than entry at 1sigma. However, overall winning ratio does not change much.

The following 2 charts compares the same model but one entries only at 1sigma and the other at 2sigma.

Build 60m Bar From 1m Bar And Events

Exchange pubishes 30 days of 1m bar of histories, which helps to build 60m bars. When market is running, exchange will push ticks at frequency on 2 ticks per second, which could be used to build 1m bar or other time scale bars.

The strategy that I’m running is using 60m bars. There’re many ways on building 60m bars. For my case, I want to build 60m bars so that they follow the mainstream market data terminal definitions, so that it would be easier to check technical indicators for validation purpose. 

Currently, the framework is using time’s minute field to check if it is divisible by 60 to conclude whether the 60m bar is completed. For example at 09:59:59.500, the next tick comes in as 10:00:00, which suggest that the 60m will be done and published to anyone who’s interested to receive the event and then start to build a new bar for the next 60m.

There’s nothing wrong with the default logic, just that the bars are different from mainstream market datda terminals. OHLCV show slightly different numbers, resulting that technical indicators are slightly different as well, which makes validation a bit difficult.

Mainstream market data terminals are using accumulation of 1m bar to 60 to build 1h bars. So basically, the 1m bars for a trading day needs to be group together with index from 1 to N. When the index is divisable by 60, the 60m bar will be published and notify any listeners who’s registered with such event. 

Note that, when session closes (entering market close or breaks), the close event will be sent, and there won’t be any open event following that. When session opens (recovering from break or market just open), the open event will be triggered.

For 60m building process, we need to check its timestamp whether it’s closing time.  

Scenario 1: Corn Sep 2021 Contract
Night market: [21:00, 23:00]
Day market: [09:00, 10:15], [10:30, 11:30], [13:30, 15:00]
Eve before the public holidays might not have night market, depending on announcement from exchanges. 

1m Bar Count:
[2100, 2300]: 120 bars
[0900,1015]: 75 bars
[1030,1130]: 60 bars
[1330,1500]: 90 bars

60m Bar Building:
1 [2100,2200]: 60 bars, ending on 2101, 2102, 2103, till 2200.
2 (2200,2300]: 60 bars, ending on 2201, 2202, 2203, till 2300.
3 [0900,1000]: 60 bars, ending on 0901, 0902, till 1000.
4 (1000,1015], 15 bars, [1030,1115] 45 bars: totals up to 60bars.
5 (1115,1130], 15 bars, [1330,1415] 45 bars: totals up to 60bars.
6 (1415,1500]: 45 bars and market closes.

When there’s no night market, then there’ll be only index 3 till 6 will be built. Normally, it will be 1-6 bars for each trading session.

Scenario 2: Silver Jun 2021 Contract Listed on SHFE (AG2106)
Night market: [21:00, 02:30]
Day market: [09:00, 10:15], [10:30, 11:30], [13:30, 15:00]

1m Bar Count:
[2100, 0230]: 120 bars + 210 bars
[0900,1015]: 75 bars
[1030,1130]: 60 bars
[1330,1500]: 90 bars

60m Bar Building:
1 [2100,2200]: 60 bars, ending on 2101, 2102, 2103, till 2200.
2 (2200,2300]: 60 bars, ending on 2201, 2202, 2203, till 2300.
3 (2300,2400]: 60 bars, ending on 2301, 2302, till 2400.
4 (0000,0100]: 60 bars.
5 (0100,0200]: 60 bars.
6 (0200,0230], 30 bars, [0900,0930] 30 bars: totals up to 60 bars.
7 (0930,1015] 45 bars, (1030,1045] 15 bars: totals up to 60 bars.
8 (1045,1130] 45 bars, [1330,1345] 15 bars: totals up to 60 bars.
9 (1345,1445]: 60 bars.
10 (1445, 1500]: 15 bars and market closes.

Scenario 3: HS300 Index Future Jun 2021 Contract Listed on CFFEX (IF2106)
Night market: NIL
Day market: [09:30, 11:30], [13:00, 15:00]

1m Bar Count:
[0930,1130]: 120 bars
[1300,1500]: 120 bars

60m Bar Building:
1 [0930,1030]: 60 bars.
2 (1030,1130]: 60 bars.
3 [1300,1400]: 60 bars.
4 (1400,1500]: 60 bars.

Note that IF has no night session, and no 10am breaks. It follows the trading hour of stock market. 

Close Event
For each of the time intervals listed in any of the above scenarios, we can trigger CloseEvent, which should recalculate all indicators, and also generate strategy signals. However, we should not just send out orders yet.

Open Event
Execute orders here.

The Open/Close events are quite complicated by itself, so I created another entry to analyze further.

Exchange Sends Ticks Of Timestamp Of Next Day

One issue that I’ve encountered when running programming trading on the CTA strategy is at the end of night session, there could be signals generated which would trigger placement orders at the non trading hours, causing annoying results:
a. it generates an invalid order, which will be rejected by the exchange.
b. the signal will not be triggered again.
c. missing the signal could make the risk to be adjusted after 1hour if I’m running 1H bars.
The cause of this issue is the funny tick that the exchange’s system is doing. At the end of the night session (typically ends at 11:00pm for DCE or ZCE), the exchange will send out a tick which has volume of 0 although the price is different from the last price usually with tick’s timestamp as 09:01am of the next morning. This tick will falsefully make the program to think that there’s a new tick coming in and hence it will send out orders base on signals.
As I observed that the next morning, 09:01 bar will be filled with valid volume again when market starts trading, so this errornous tick can’t be observed by then.
The solution that I could think of is to ignore the tick with volume=0, or if you play safe, to ignore tick with volume=0 at start time with minute=1. I will go with the formal approach for now, since the contract that I’ve been monitoring are liquid contracts. 
Apparently, there’re others who are discussion about this special errornous tick in forums as well, which I’ve listed in reference. Some of them are using volume to filter ticks (like what I’m going to do); and some of them are using prices (which is depending on the software itself since tick itself does not have high/low).

Reference:
1. vnpy on volume: https://github.com/vnpy/vnpy/issues/254
2. tbquant on opening session: http://www.tradeblazer.net/article/45.html#%E9%9B%86%E5%90%88%E7%AB%9E%E4%BB%B7%E6%95%B0%E6%8D%AE%E8%BF%87%E6%BB%A4
3. joinquant on errorneous tick data: https://test.demo.joinquant.com/view/community/detail/38d849982caa8ba71b048d7475c0f22b?type=2