Support Board
Date/Time: Tue, 22 Apr 2025 15:16:58 +0000
[Programming Help] - Annoying problem with Heiken Ashi and ACSIL (for SC engineer)
View Count: 240
[2025-01-08 16:00:21] |
BenjFlame - Posts: 337 |
EDIT: This issue has nothing to do with Heiken Ashi. See solution in message 8 below. Hi, When Heiken Ashi is set to "Display as main price Graph", it is impossible to access Heiken Ashi OHLC values: I always get the underlying price OHLC, even when accessing OHLC values from the indicator itself. This causes many problems. At least, when accessing the indicator OHLC from ACSIL (and not underlying price data), I should get the Heiken values, not underlying price value. Adding a second Heiken Ashi study is not a solution, as its plot is not the same as first one. What's your take on this? Date Time Of Last Edit: 2025-01-09 16:07:44
|
[2025-01-08 16:24:50] |
User431178 - Posts: 654 |
When Heiken Ashi is set to "Display as main price Graph", it is impossible to access Heiken Ashi OHLC values: I always get the underlying price OHLC, even when accessing values from the indicator itself.
Are you sure about that? SCSFExport scsf_hk_values(SCStudyInterfaceRef sc) { auto& in_study_id = sc.Input[0]; if (sc.SetDefaults) { sc.GraphName = "Chart Values"; sc.GraphRegion = 0; sc.AutoLoop = 0; sc.ScaleRangeType = SCALE_AUTO; sc.DisplayStudyInputValues = 0; sc.CalculationPrecedence = VERY_LOW_PREC_LEVEL; in_study_id.Name = "Study ID"; in_study_id.SetStudyID(0); return; } auto refData = SCGraphData{}; sc.GetStudyArraysFromChartUsingID(sc.ChartNumber , in_study_id.GetStudyID() , refData); if (sc.UpdateStartIndex == 0) { const auto open = sc.FormatGraphValue(sc.Open[sc.ArraySize - 1], sc.BasedOnGraphValueFormat); const auto high = sc.FormatGraphValue(sc.High[sc.ArraySize - 1], sc.BasedOnGraphValueFormat); const auto low = sc.FormatGraphValue(sc.Low[sc.ArraySize - 1], sc.BasedOnGraphValueFormat); const auto last = sc.FormatGraphValue(sc.Close[sc.ArraySize - 1], sc.BasedOnGraphValueFormat); auto msg = SCString{}; msg.Format("Main Chart Values at index %d | Open: %s, High: %s, Low: %s, Last: %s" , sc.ArraySize - 1 , open.GetChars() , high.GetChars() , low.GetChars() , last.GetChars()); sc.AddMessageToLog(msg, 0); if (refData.GetArraySize() == 0) return; const auto open_ref = sc.FormatGraphValue(refData[SC_OPEN][sc.ArraySize - 1], sc.BasedOnGraphValueFormat); const auto high_ref = sc.FormatGraphValue(refData[SC_HIGH][sc.ArraySize - 1], sc.BasedOnGraphValueFormat); const auto low_ref = sc.FormatGraphValue(refData[SC_LOW][sc.ArraySize - 1], sc.BasedOnGraphValueFormat); const auto last_ref = sc.FormatGraphValue(refData[SC_LAST][sc.ArraySize - 1], sc.BasedOnGraphValueFormat); msg.Format("HK Values at index %d | Open: %s, High: %s, Low: %s, Last: %s" , sc.ArraySize - 1 , open_ref.GetChars() , high_ref.GetChars() , low_ref.GetChars() , last_ref.GetChars()); sc.AddMessageToLog(msg, 0); } } 2025-01-08 10:19:51.416 | Chart: NQH25-CME[M] 1 Min #1 | Study: Chart Values | Main Chart Values at index 24856 | Open: 21319.75, High: 21324.50, Low: 21319.75, Last: 21323.00 2025-01-08 10:19:51.416 | Chart: NQH25-CME[M] 1 Min #1 | Study: Chart Values | HK Values at index 24856 | Open: 21314.32, High: 21324.50, Low: 21314.32, Last: 21323.00 2025-01-08 10:19:58.176 | Chart: NQH25-CME[M] 1 Min #1 Heikin-Ashi | Study: Chart Values | Main Chart Values at index 24856 | Open: 21314.32, High: 21324.50, Low: 21314.32, Last: 21323.00 2025-01-08 10:19:58.176 | Chart: NQH25-CME[M] 1 Min #1 Heikin-Ashi | Study: Chart Values | HK Values at index 24856 | Open: 21314.32, High: 21324.50, Low: 21314.32, Last: 21323.00 Rows 1 and 2 above are with HK as sub-chart, rows 3 and 4 are with HK as main chart. When HK is main chart, values return for OHLC are same for HK and chart. |
[2025-01-08 16:34:06] |
BenjFlame - Posts: 337 |
2025-01-08 10:19:58.176 | Chart: NQH25-CME[M] 1 Min #1 Heikin-Ashi | Study: Chart Values | Main Chart Values at index 24856 | Open: 21314.32, High: 21324.50, Low: 21314.32, Last: 21323.00 2025-01-08 10:19:58.176 | Chart: NQH25-CME[M] 1 Min #1 Heikin-Ashi | Study: Chart Values | HK Values at index 24856 | Open: 21314.32, High: 21324.50, Low: 21314.32, Last: 21323.00 That's the problem: when HK is main price, it's values are those of the main price and not of HK candles. Indeed, when I put it on subchart, I can access HK values. But I need HK as main chart (to color bars). And when adding additional HK as subchart.... subchart HK candles are different than main HK candles. Date Time Of Last Edit: 2025-01-08 16:36:38
|
[2025-01-08 16:40:32] |
User431178 - Posts: 654 |
That's the problem: when HK is main price, it's values are those of the main price and not of HK candles.
You've got it backwards. When HK is the main chart, you cannot get the underlying OHLC values, instead you get HK-OHLC from both main chart and study. |
[2025-01-08 17:00:48] |
BenjFlame - Posts: 337 |
I was using sc.BaseData[SC_LOW][sc.Index] syntax to reference data, could this have been the problem ? I notice different values when I use sc.Low[sc.ArraySize] syntax.
|
[2025-01-08 17:14:53] |
User431178 - Posts: 654 |
I wouldn't think so, as sc.Low is an alias or direct ref to sc.BaseData[SC_LOW]. sc.BaseData[SC_LOW][sc.Index]
is the same assc.Low[sc.Index]
|
[2025-01-08 18:37:11] |
BenjFlame - Posts: 337 |
You are right. Thanks. The problem is elswhere. // Agressive Scratch Exit if (ButtonEnableExitAgressiveScratchEnabled) { // Unpush state of button after second bar since entry if (Quantity != 0 && sc.GetBarsSinceLastTradeOrderEntry() > 1) { sc.SetCustomStudyControlBarButtonEnable(EnableExitAgressiveScratch, 0); return; } if (sc.GetBarsSinceLastTradeOrderEntry() == 1) { // No position if (Quantity == 0) { return; } // Exit Short if (Quantity < 0) { if (currentPrice > sc.BaseData[SC_LOW][sc.Index - 2]) /// HERE { SCString rrr; rrr.Format("CurrentPriceLine=%f", currentPrice); sc.AddMessageToLog(rrr, 0); SCString ttt; ttt.Format("BarSinceEntry=%d", sc.GetBarsSinceLastTradeOrderEntry()); sc.AddMessageToLog(ttt, 0); SCString DebugMessage; DebugMessage.Format("Low2Bars0=%f", sc.BaseData[SC_LOW][sc.Index]); sc.AddMessageToLog(DebugMessage, 0); SCString DebugMessage1; DebugMessage1.Format("Low2Bars1=%f", sc.BaseData[SC_LOW][sc.Index - 1]); sc.AddMessageToLog(DebugMessage1, 0); SCString DebugMessage2; DebugMessage2.Format("Low2Bars2=%f", sc.BaseData[SC_LOW][sc.Index - 2]); sc.AddMessageToLog(DebugMessage2, 0); SCString DebugMessage3; DebugMessage3.Format("Low2Bars3=%f", sc.BaseData[SC_LOW][sc.Index - 3]); sc.AddMessageToLog(DebugMessage3, 0); int Result = sc.FlattenPosition(); sc.CancelAllOrders(); CreateMessageLog(sc, "ShortExit Aggressive Scratch: OK"); if (Result != 1) { CreateMessageLog(sc, "Aggressive Scratch Short Exit: Trade didn't pass"); sc.SetCustomStudyControlBarButtonEnable(EnableExitAgressiveScratch, 0); return; } sc.SetCustomStudyControlBarButtonEnable(EnableExitAgressiveScratch, 0); return; } } } return; } in this code if (currentPrice > sc.BaseData[SC_LOW][sc.Index - 2]) /// HERE When I change the index, to let's say -1, then the values logged below for the lows of the few previous bar change. I don't get why. It should all be calculated from sc.GetBarsSinceLastTradeOrderEntry() == 1 so I see no reason why I can't reference correct bar low. Does what I said even makes sense :/ ? Date Time Of Last Edit: 2025-01-08 18:37:32
|
[2025-01-09 16:05:33] |
BenjFlame - Posts: 337 |
Turns out the problem was completely different than what I thought. Here is the explanation, thanks to OpenAI o1 model: Short Answer Yes, there is indeed a subtle logic flaw: you are relying on sc.Index - 1 or sc.Index - 2 to refer to the “entry bar” or a specific prior bar, but in reality you are in an autoloop study where Sierra Chart itself advances sc.Index in ways that don’t always match “the bar of entry ± 1.” As soon as a new bar starts, or if your study is processing partial bars, you can end up referencing a bar or two further ahead (or behind) than intended. Simply put, “bar since entry = 1” does not guarantee that the entry occurred precisely at [sc.Index - 1]. Why This Happens 1.Autoloop Behavior in Sierra Chart When you use sc.AutoLoop = 1, Sierra Chart calls your function “once per bar” (sometimes more if a bar is still forming). This means sc.Index increments automatically as new bars form, but can also temporarily point to earlier bars during chart reload or partial updates. 2.Timing of Orders vs. Bar Index -You might place an order in the middle of the current bar (intrabar). Then, at the close of that bar, sc.GetBarsSinceLastTradeOrderEntry() will become 1. -Because of partial updates (e.g., ticks inside a bar), your code might run more than once, and sc.Index might increment from intrabar updates. By the time your condition sees “bars since entry = 1,” you might actually be on bar sc.Index that is one or two bars ahead of what you assume if we only think in full bars. 3.Referencing sc.BaseData with (sc.Index - X) -You assume that “the bar of the entry” is [sc.Index - 1] or [sc.Index - 2]. But if an intrabar update has advanced sc.Index, then [sc.Index - 2] could actually be the same bar in which you placed the order, or it could be another bar entirely. -Likewise, once you switch from -2 to -1 or vice versa, the logs about “previous bar highs/lows” change in ways that you don’t expect because Sierra Chart is counting bars differently under the hood. In short, your code is mixing “bars since entry” with “sc.Index - some offset,” but these two concepts do not always line up one-to-one. How to Fix or Work Around It A robust approach is to store the bar index at the time the order is placed, and then reference that stored index explicitly later. For example: When you actually submit the trade, record the bar index: int MyEntryBarIndex = sc.Index; In subsequent bars, if you want the low of the bar right before the entry bar, you can do: float priorBarLow = sc.BaseData[SC_LOW][MyEntryBarIndex - 1];
Or if you want one or two bars after the entry bar, use: float barAfterEntryLow = sc.BaseData[SC_LOW][MyEntryBarIndex + 1];
float barTwoAfterEntryLow = sc.BaseData[SC_LOW][MyEntryBarIndex + 2]; Use your stored index in any conditionals (e.g., for the “aggressive scratch” logic), instead of relying on “sc.Index - 1” or “sc.Index - 2.” This way, you know exactly which bar you’re referencing, regardless of how Sierra Chart’s autoloop or partial updates advance sc.Index. Key Takeaways 1.“Bars since entry = 1” means the study is running on the first bar after the entry bar has fully closed. It does not necessarily mean the current loop’s “sc.Index - 1” is the entry bar. 2.Storing the entry bar index when a trade is placed is safer than referencing “sc.Index - X”. Logged highs/lows differ when you switch from “-2” to “-1” because sc.Index can be one or two bars ahead of your assumption—especially with intrabar updates. 3.By decoupling “bars since entry” from your direct array indexing (“sc.Index - something”), you’ll avoid the shifting references and you will observe consistent high/low logs for the bar(s) you actually want. Indeed, storing the index of entry bar at moment of entry in persistent variable, and using it to reference bars from entry bar solutionned the problem. Date Time Of Last Edit: 2025-01-09 16:11:57
|
To post a message in this thread, you need to log in with your Sierra Chart account: