Support Board
Date/Time: Sun, 12 Jul 2026 18:47:49 +0000
Post From: Having Volume Profile and Delta Profile side by side.
| [2026-07-03 22:14:24] |
| rc_camilo - Posts: 8 |
|
I used Grok Build with a proper defined goal to create this version (created on the first pass). It is simple. You only define the colors for BID/ASK and the number of ticks to group. It is what I need, so I am not improving it in any ways. Once the study is added to the chart, it will plot the delta profile to the left of the first bar the profile is drawn. I will send the code here in case people find it usefull. Thanks //============================================================================= // ROD176 - Delta Profile (left of Volume Profile tool) //============================================================================= // Detects user-drawn Volume Profile tools (DRAWING_VOLUME_PROFILE) on the chart. // For each such tool's time range (BeginDateTime to EndDateTime), computes the // delta profile (AskVolume - BidVolume aggregated per price level) using the // same bars from VolumeAtPriceForBars. // Draws the delta profile using GDI (sc.Graphics) anchored at the left of the // start bar of the range, extending LEFTWARD (negative x direction). // Positive delta uses "Ask Color" (color for asks), negative uses "Bid Color" (color for bids). // "Number of Ticks to Combine" bins the price levels (e.g. 1 = per tick, 2=group 2 ticks). // Updates live when the VP tool is modified/moved (UpdateAlways=1). // Multiple concurrent VP tools supported. // // Matches patterns from ROD16x delta/VP detection studies and ROD168/163 GDI profiles. // //============================================================================= #define NOMINMAX #include "sierrachart.h" #include <map> #include <algorithm> #include <cmath> SCDLLName("ROD176 - Delta Profile"); // GDI drawing forward declaration void DrawDeltaProfileToChart(HWND WindowHandle, HDC DeviceContext, SCStudyInterfaceRef sc); SCSFExport scsf_ROD176DeltaProfile(SCStudyInterfaceRef sc) { // Inputs SCInputRef AskColorInput = sc.Input[0]; SCInputRef BidColorInput = sc.Input[1]; SCInputRef TicksCombineInput = sc.Input[2]; if (sc.SetDefaults) { sc.GraphName = "ROD176 - Delta Profile (left of VP)"; sc.StudyDescription = "ROD176-DeltaProfile\n\n" "Detects drawn Volume Profile tools and draws matching delta profile to the LEFT of the range start.\n" "Delta = Ask - Bid per (binned) price level for the exact same time range.\n" "Uses colors for bids and asks. Drawn using GDI anchored left of Begin bar, extending further left.\n" "Changes to VP tool immediately update the delta profile."; sc.AutoLoop = 0; // Manual control, we react to drawings sc.GraphRegion = 0; sc.UpdateAlways = 1; // Important for live reflection of tool changes sc.MaintainVolumeAtPriceData = 1; // REQUIRED to access VolumeAtPriceForBars sc.p_GDIFunction = DrawDeltaProfileToChart; AskColorInput.Name = "Ask Color (Positive Delta) - color for asks"; AskColorInput.SetColor(RGB(0, 180, 0)); // Green for ask/positive BidColorInput.Name = "Bid Color (Negative Delta) - color for bids"; BidColorInput.SetColor(RGB(200, 0, 0)); // Red for bid/negative TicksCombineInput.Name = "Number of Ticks to Combine"; TicksCombineInput.SetInt(1); TicksCombineInput.SetIntLimits(1, 100); return; } // Nothing heavy here - detection + draw happens in GDI callback on updates // (GDI is called after this for drawing) } // The GDI function does the work: re-detects tools, computes delta for the range, draws to left void DrawDeltaProfileToChart(HWND /*WindowHandle*/, HDC /*DeviceContext*/, SCStudyInterfaceRef sc) { const COLORREF AskColor = sc.Input[0].GetColor(); const COLORREF BidColor = sc.Input[1].GetColor(); const int CombineTicks = sc.Input[2].GetInt(); if (CombineTicks < 1) return; // Note: do not early-return on global VAP emptiness here. // Per-tool / per-bar checks handle ranges that may have VAP data. // (This fixes suppression bug when VAP reports 0 at check time but tools have data.) int DrawingIndex = 0; s_UseTool Tool; Tool.Clear(); // Loop over all Volume Profile tools currently drawn on this chart while (sc.GetUserDrawnChartDrawing(sc.ChartNumber, DRAWING_VOLUME_PROFILE, Tool, DrawingIndex++)) { SCDateTime BeginDT = Tool.BeginDateTime; SCDateTime EndDT = Tool.EndDateTime; if (BeginDT == 0 || EndDT == 0 || EndDT <= BeginDT) continue; // Map datetimes to bar indices (containing bars) int StartBar = sc.GetContainingIndexForSCDateTime(sc.ChartNumber, BeginDT.GetAsDouble()); int EndBar = sc.GetContainingIndexForSCDateTime(sc.ChartNumber, EndDT.GetAsDouble()); if (StartBar < 0 || EndBar < StartBar || StartBar >= sc.ArraySize) continue; if (EndBar >= sc.ArraySize) EndBar = sc.ArraySize - 1; // Origin X: left side of the start bar (where VP starts, we draw left of it) int OriginX = sc.BarIndexToXPixelCoordinate(StartBar); // Build binned delta map for bars in [StartBar .. EndBar] // key = binned PriceInTicks, value = cumulative delta std::map<int, double> DeltaBins; for (int bar = StartBar; bar <= EndBar; ++bar) { int numLevels = sc.VolumeAtPriceForBars->GetSizeAtBarIndex(bar); if (numLevels <= 0) continue; for (int lev = 0; lev < numLevels; ++lev) { const s_VolumeAtPriceV2* pVAP = nullptr; if (!sc.VolumeAtPriceForBars->GetVAPElementAtIndex(bar, lev, &pVAP) || pVAP == nullptr) continue; int rawTick = pVAP->PriceInTicks; // Bin: floor to multiple of CombineTicks int binnedTick = (rawTick / CombineTicks) * CombineTicks; double barDeltaAtLevel = (double)pVAP->AskVolume - (double)pVAP->BidVolume; DeltaBins[binnedTick] += barDeltaAtLevel; } } if (DeltaBins.empty()) continue; // Scale: find max abs delta, target a reasonable left width (no extra input) double maxAbs = 0.0; for (const auto& kv : DeltaBins) { double a = (kv.second < 0 ? -kv.second : kv.second); if (a > maxAbs) maxAbs = a; } if (maxAbs < 0.0001) continue; const int MaxProfileWidth = 110; // pixels for max bar - reasonable default for left side double scale = (double)MaxProfileWidth / maxAbs; // Region coords for clipping int region = sc.GraphRegion; int topClip = (int)sc.StudyRegionTopCoordinate; int botClip = (int)sc.StudyRegionBottomCoordinate; // Draw each binned level as a horizontal bar extending LEFT from OriginX for (const auto& kv : DeltaBins) { double deltaVal = kv.second; double ad = (deltaVal < 0 ? -deltaVal : deltaVal); if (ad < 0.0001) continue; int binnedTick = kv.first; double centerPrice = (double)binnedTick * sc.TickSize; // Vertical span for this bin ( +/- half combine ticks ) double halfBinPrice = (CombineTicks * sc.TickSize) * 0.5; int yTop = sc.RegionValueToYPixelCoordinate((float)(centerPrice + halfBinPrice), region); int yBot = sc.RegionValueToYPixelCoordinate((float)(centerPrice - halfBinPrice), region); if (yTop > yBot) { int t = yTop; yTop = yBot; yBot = t; } // Clip if (yTop < topClip) yTop = topClip; if (yBot > botClip) yBot = botClip; if (yTop >= yBot) continue; // Width proportional to |delta| int barWidth = 2; if (ad * scale > 2) barWidth = (int)(ad * scale + 0.5); // Always draw to the LEFT of the origin int xLeft = OriginX - barWidth; int xRight = OriginX; // Choose color: AskColor for positive (ask-dominant / buy pressure), BidColor for negative COLORREF col = (deltaVal > 0.0) ? AskColor : BidColor; // Use modern Graphics interface (matches ROD168 / GDIExample) -- setup hoisted style, color per bin n_ACSIL::s_GraphicsBrush gBrush; gBrush.m_BrushType = n_ACSIL::s_GraphicsBrush::BRUSH_TYPE_SOLID; int r = (col & 0xFF); int g = ((col >> 8) & 0xFF); int b = ((col >> 16) & 0xFF); gBrush.m_BrushColor.SetRGB(r, g, b); sc.Graphics.SetBrush(gBrush); n_ACSIL::s_GraphicsPen gPen; gPen.m_PenColor.SetRGB(r, g, b); gPen.m_Width = 1; gPen.m_PenStyle = n_ACSIL::s_GraphicsPen::e_PenStyle::PEN_STYLE_SOLID; sc.Graphics.SetPen(gPen); // Draw filled rectangle for the delta bar at this price level sc.Graphics.DrawRectangle(xLeft, yTop, xRight, yBot); } } } // End of ROD176-DeltaProfile.cpp |
