The idea is to account for the bid/ask spread and avoid selling at the ask and buying at the bid.
The "SpreadStrategy.cs" class is intended as base class to derive strategies from.
The "SampleMACrossOverReversion.cs" is a modification of the standard SampleMACrossOver NT example strategy which allows mean reversion.
"SampleMACrossOverReversion.cs" does not wait for upticks or downticks and just submits orders straight away. Because of this, there are many trades where the buy was at the same price as the sell or where the buy was at a bid price and the sell was at an ask price.
The "SampleMACrossOverSpread.cs" is just like the "SampleMACrossOverReversion.cs" apart from it implements uptick/downtick enforcement for trades using the "SpreadStrategy.cs" class.
Backtesting over 1 second bars for 2010 yields extremely different results using the two methods, as expected.
Trading extremely fast, such as a mean reverting 10/25 MACO on 1 second data is expected to generate massive losses due to the BID/ASK spread cost.
"SampleMACrossOverSpread.cs" does generate huge losses (greater than $3M) due enforcing that shorts get filled on the next downtick (which means it is at the BID since we are using Trade/last Tick data) and longs get filled on the next uptick (which means it is the ASK).
"SampleMACrossOverReversion.cs" however, generates a profit (greater than $173K) in the backtest.
I reckon that the profits in "SampleMACrossOverReversion.cs" are artificial and would not occur in live trading. (see .PNG image).
I reckon also that in live trading, the profits would look more like "SampleMACrossOverSpread.cs". (see .PNG image).
I may have made an error or there may be a bug. Who knows.
This is the juice...
SpreadStrategy.cs:
/// <summary> /// This is sealed by the SpreadStrategy class. Override OnBarUpdateSpread() instead. /// </summary> protected sealed override void OnBarUpdate() { // User can submit orders here just as with OnBarUpdate() OnBarUpdateSpread(); // Bars.GetOpen(CurrentBar+1) is the price at which any orders submitted to NT will be filled. // Close[0] is 1-tick in the past. NT does not fill at Close[0]. var newPrice = Bars.GetOpen(CurrentBar+1); // BUY orders can only be executed on an uptick so the only get submitted if the open of the bar // (which just opened and triggered this call to OnBarUpdate()). For this reason we delay the NT // submission until there is an uptick. The same applies to SELL orders and downticks. if(newPrice > Price) { SubmitLongEntries(); // These are BUY orders SubmitShortExits(); // These are BUY orders } if(newPrice < Price) { SubmitShortEntries(); // These are SELL orders SubmitLongExits(); // These are SELL orders } this.Price = newPrice; }
protected override void OnBarUpdateSpread() { if (CurrentBar < BarsRequiredToTrade) return; if (CrossAbove(smaFast, smaSlow, 1)) { ISubmit submit = new Submit(); if(MeanReversion) { base.SubmitEntryShort(submit); } else { base.SubmitEntryLong(submit); } } else if (CrossBelow(smaFast, smaSlow, 1)) { ISubmit submit = new Submit(); if(MeanReversion) { base.SubmitEntryLong(submit); } else { base.SubmitEntryShort(submit); } } }
Related:
Comment