Login Page - Create Account

Support Board


Date/Time: Sun, 19 May 2024 03:16:11 +0000



[Programming Help] - bar backtest: is there a way to adjust the line-delay/time-to-order-in-force delay?

View Count: 1019

[2019-03-26 22:45:20]
uM8137 - Posts: 180
Say I'm doing a bar-based backtest, with 1 minute bars. Suppose in my ACSIL trading code at sc.Index = 100,
I decide to buy based on the the sc.Index = 100 values for the high, low, open, and close prices to
place a market trade. Now I note that the 1min bar simulator can award me the open price
(from 60 seconds back in time), also from the sc.Index = 100 bar.

Effectively this makes the simulation very overly
optimistic because I'm computing "in the future" and getting awarded a past price,
which is (usually) no longer available --in particular if the market is moving.

So to my question: is the a line-delay simulation parameter that would allow me to say how long between
the start (or end) of the bar that is issuing the order and when the market price for the order is determined? While commonly needed for sub-second modeling, I could use it here to correct the minute sim (by setting it to +60 seconds from the open of the current 1 minute bar, or +100 microseconds from the close if the time-from-close instead) to get more realistic fills on my market orders during the 1min bar-based sim.

Or are there other alternatives for correcting this problem? I tried to think if I could ask for a limit order at the closing price of the bar, but I don't think that solves the problem because the system could still fill me below (for a buy) the stated limit on the order, doing so legally.
[2019-03-27 03:09:18]
Sierra Chart Engineering - Posts: 104368
It sounds as though you will really want to do a replay back test with tick by tick data instead:
Auto Trade System Back Testing
Sierra Chart Support - Engineering Level

Your definitive source for support. Other responses are from users. Try to keep your questions brief and to the point. Be aware of support policy:
https://www.sierrachart.com/index.php?l=PostingInformation.php#GeneralInformation

For the most reliable, advanced, and zero cost futures order routing, *change* to the Teton service:
Sierra Chart Teton Futures Order Routing
Date Time Of Last Edit: 2019-03-27 03:10:08
[2019-03-27 13:12:16]
uM8137 - Posts: 180
Yes, but I was hoping the speed of bar backtest could be retained and used to get an accurate first impression.

It does seem to be a rather serious problem that the bar-backtest awards prices from the past
(or conversely, predictions from the future).

I hacked around the issue by recording the buy/sell decision at sc.Index-1 and then not acting on
it until sc.Index, but this won't translate well to live trading.
[2019-03-30 18:12:43]
uM8137 - Posts: 180
https://www.dropbox.com/s/aep4v0ufwedtbdu/Screenshot%202019-03-30%2018.48.12.png?dl=0

http://www.sierrachart.com/image.php?Image=1553968643225.png

In this example, I'm trying to understand how this 1min bar simulation fill is not a bug in the backtest fill logic.

In the above chart, the right side orange down-arrow is a SELL simulation order at 2019-March-25, 04:25:00am UTC time on F.US.EPM19.

The trade log (visible on the dropbox screenshot) clearly shows a fill at 2796.50 USD. That price was not observed at all during the 2019-March-25 04:25:00 UTC bar!

The *only* point when price was near that fill was one minute earlier at 4:24. I'm getting "prices from the past". These awarded fills over a minute stale, and very off.

The simulation was done from 2019-March-25T04:00:00 UTC - 2019-March-25T23:55:00 UTC, and the trading system study to reproduce these trades here is a very simple saw tooth test pattern that buys and sells alternatively every 5 minutes. It is pasted below. The data feed for this /ES contract is from CQG, but SierraChart input 1min bars should be identical.

Please let me know if there is any reasonable interpretation. Getting prices from the past is equivalent to predicting on future data. A huge bugaboo.

#include "sierrachart.h"

SCDLLName("sawtooth_buy_0_and_sell_5")

#define vv(args...) \
do {SCString Msg; Msg.Format(args); sc.AddMessageToLog(Msg, 0);} while(0);

void showArray(SCStudyInterfaceRef sc, const char* name, SCFloatArray arr, int beg, int end) {
for (int i = beg; i < end; i++) {
vv("%s[%i]=%g", name, i, arr);
}
}

int getCurrentPos(SCStudyInterfaceRef sc) {
s_SCPositionData pos;
sc.GetTradePosition(pos);
int curPos = pos.PositionQuantity;
return curPos;
}

SCSFExport scsf_Saw_Buy_on_0_and_Sell_on_5(SCStudyInterfaceRef sc)
{
SCInputRef MaxContract = sc.Input[0];

// Subgraphs
SCSubgraphRef Buy = sc.Subgraph[0];
SCSubgraphRef Sell = sc.Subgraph[1];

SCSubgraphRef Debug = sc.Subgraph[2];

// above are recommended, below are those we took based
// on our state of already being maxed or not.
SCSubgraphRef TakenBuy = sc.Subgraph[3];
SCSubgraphRef TakenSell = sc.Subgraph[4];

int BarStartTime = sc.BaseDateTimeIn.TimeAt(sc.Index);
int minuteOfDay = BarStartTime / 60;
int sec = BarStartTime % 60;

// Set configuration variables
if (sc.SetDefaults)
{
sc.SupportReversals = 1;
// Unmanaged Automated Trading
sc.AllowMultipleEntriesInSameDirection =1;

sc.AllowOppositeEntryWithOpposingPositionOrOrders =1; // default is true.

sc.CancelAllOrdersOnEntriesAndReversals =0;
sc.AllowEntryWithWorkingOrders =1;
sc.AllowOnlyOneTradePerBar =0;

sc.MaximumPositionAllowed = 10;


sc.GraphName = "sawtooth_buy_at_0_sell_at_5";
sc.StudyDescription = "every minute after minute, buy on the 0, sell on the 5 timestamps";
sc.GraphRegion = 0;

MaxContract.Name = "MaxContract";
MaxContract.SetInt(1);
MaxContract.SetIntLimits(1, 100000);
MaxContract.SetDescription("The number of contracts transact");

Buy.Name = "Buy";
Buy.PrimaryColor = COLOR_GREEN;
Buy.DrawStyle = DRAWSTYLE_ARROWUP;
Buy.LineWidth = 2;

Sell.Name = "Sell";
Sell.DrawStyle = DRAWSTYLE_ARROWDOWN;
Sell.PrimaryColor = COLOR_RED;
Sell.LineWidth = 2;

Debug.Name = "Debug";
//Debug.DrawStyle = DRAWSTYLE_ARROWDOWN;
Debug.PrimaryColor = COLOR_PURPLE;
Debug.LineWidth = 1;


TakenBuy.Name = "TakenBuy";
TakenBuy.PrimaryColor = COLOR_YELLOW;
TakenBuy.DrawStyle = DRAWSTYLE_ARROWUP;
TakenBuy.LineWidth = 3;
TakenBuy.DrawZeros = false;

TakenSell.Name = "TakenSell";
TakenSell.DrawStyle = DRAWSTYLE_ARROWDOWN;
TakenSell.PrimaryColor = COLOR_ORANGE;
TakenSell.LineWidth = 3;
TakenSell.DrawZeros = false;

// Auto Looping On
sc.AutoLoop = 1;

// During development only set to 1
sc.FreeDLL = 1;

// want end times of bars too.
sc.MaintainAdditionalChartDataArrays = 1;

// Must return
return;
}

// Get the date
int BarDate = sc.BaseDateTimeIn.DateAt(sc.Index);

// Get the End DateTime at the current index.
SCDateTime BarEndDateTime = sc.BaseDataEndDateTime[sc.Index];
int BarEndTime = BarEndDateTime.GetTime(); // always returns 0. huh. why?

// bounce saw-tooths between 0 and 5. we will buy at 0, and sell at 5.
int saw = minuteOfDay % 10;
if (saw >= 5) {
saw = 10 - saw; // make a saw-tooth pattern.
}

double close = sc.Close[sc.Index];
double hi = sc.BaseData[SC_HIGH][sc.Index];
double lo = sc.BaseData[SC_LOW][sc.Index];
double op = sc.BaseData[SC_OPEN][sc.Index];


//vv("at sc.Index %i, close is %f", sc.Index, close);

Debug[sc.Index] = 2800 + saw; // 2800 so we are visible on the /ES graph 2019 March 25


int maxcon = MaxContract.GetInt();
sc.MaximumPositionAllowed = 10;

Buy[sc.Index] = 0;
Sell[sc.Index] = 0;
int curPos = getCurrentPos(sc);

if (saw == 5) {
// time to sell

Sell[sc.Index] = sc.High[sc.Index] + sc.TickSize;

if (curPos > -maxcon) {
// room to sell
int qty = maxcon;

vv("at simtm %02i:%02i:%02i at sc.Index %i, saw is %i, time to sell, curPos=%i, orderQty=%i; close=%f, op=%f, hi=%f, lo=%f, sc.LastTradePrice=%f", minuteOfDay / 60, minuteOfDay % 60, sec, sc.Index, saw, curPos, qty, close,op,hi,lo,sc.LastTradePrice);

// actual sell order entry.
// Create an s_SCNewOrder object.
// Automated Trading From an Advanced Custom Study: s_SCNewOrder Structure Members
s_SCNewOrder NewOrder;
// qty == 0 for BuyExit or SellExit means flatten the position to zero.
NewOrder.OrderQuantity = qty;
NewOrder.OrderType = SCT_ORDERTYPE_MARKET;
NewOrder.TimeInForce = SCT_TIF_GTC;
NewOrder.Price1 = 0;
NewOrder.Price2 = 0;

    int Result = (int)sc.SellEntry(NewOrder);
    if (Result > 0) {
//If there has been a successful order entry, then draw an arrow at the high of the bar.
TakenSell[sc.Index] = sc.High[sc.Index] + 2*sc.TickSize;

int updatedPos = getCurrentPos(sc);
vv("at simtm %02i:%02i:%02i successful sell size=%i, at BarStartTime=%i, Result=%i, prevPos=%i, updatedPos=%i", minuteOfDay / 60, minuteOfDay % 60, sec, qty, BarStartTime, Result, curPos, updatedPos);

} else { //order error
int updatedPos = getCurrentPos(sc);
vv("at simtm %02i:%02i:%02i failed to sell size=%i, at BarStartTime=%i, Result=%i, prevPos=%i, updatedPos=%i", minuteOfDay / 60, minuteOfDay % 60, sec, qty, BarStartTime, Result, curPos, updatedPos);
sc.AddMessageToLog(sc.GetTradingErrorTextMessage(Result), 0);
}
}
}

if (saw == 0) {
// time to buy
Buy[sc.Index] = sc.Low[sc.Index] - sc.TickSize;

if (curPos < maxcon) {
// room to buy
int qty = maxcon;

vv("at simtm %02i:%02i:%02i at sc.Index %i, saw is %i, time to buy, curPos=%i, orderQty=%i; close=%f, op=%f, hi=%f, lo=%f, sc.LastTradePrice=%f", minuteOfDay / 60, minuteOfDay % 60, sec, sc.Index, saw, curPos, qty, close,op,hi,lo, sc.LastTradePrice);
// Create an s_SCNewOrder object.
s_SCNewOrder NewOrder;
NewOrder.OrderQuantity = qty;
NewOrder.OrderType = SCT_ORDERTYPE_MARKET;
NewOrder.TimeInForce = SCT_TIF_GTC;
NewOrder.Price1 = 0;
NewOrder.Price2 = 0;

    int Result = (int)sc.BuyEntry(NewOrder);
    if (Result > 0) {//If there has been a successful order entry, then draw an arrow at the low of the bar.
TakenBuy[sc.Index] = sc.Low[sc.Index] - 2*sc.TickSize;

int updatedPos = getCurrentPos(sc);
vv("at simtm %02i:%02i:%02i successful buy size=%i, at BarStartTime=%i, Result=%i, prevPos=%i, updatedPos=%i", minuteOfDay / 60, minuteOfDay % 60, sec, qty, BarStartTime, Result, curPos, updatedPos);

} else { //order error
int updatedPos = getCurrentPos(sc);
vv("at simtm %02i:%02i:%02i failed to buy size=%i, at BarStartTime=%i, Result=%i, prevPos=%i, updatedPos=%i", minuteOfDay / 60, minuteOfDay % 60, sec, qty, BarStartTime, Result, curPos, updatedPos);
sc.AddMessageToLog(sc.GetTradingErrorTextMessage(Result), 0);
}
}
}
}

Date Time Of Last Edit: 2019-03-30 18:20:25
[2019-03-30 21:07:31]
uM8137 - Posts: 180
Ah hah! Adding this code snippet to insist on only processing finished bars

if(sc.GetBarHasClosedStatus(sc.Index) != BHCS_BAR_HAS_CLOSED) {
return;
}

early in the study, so we leave the function before any orders are placed if the bar is not "closed" yet, substantially addresses the wrong price problem.

To post a message in this thread, you need to log in with your Sierra Chart account:

Login

Login Page - Create Account