// ============================================================================ // Original Study: // AtlasLargeTradesProV2_4_SubgraphBubbles.cpp by User385376 5/6/2026 // Original aggregation/rendering architecture preserved and modified. // // Purpose: // Large trade bubbles for Sierra Chart with same-day historical rebuilding. // - Rebuilds the current chart date from Sierra's intraday tick records. // - Continues processing live Time & Sales after the rebuild. // - Draws buy/sell aggression bubbles historically so trades can be reviewed. // - Uses Sierra subgraph variable-size circles so Transparency and Hollow // Bubble settings work correctly. // // Notes: // SC_TS_ASK = aggressive buyer lifted the offer. // SC_TS_BID = aggressive seller hit the bid. // // Modified Version: // Atlas Large Trades TAS // // Modifications: // - Replaced rolling millisecond aggregation with TAS style combining. // - Added combining based on: // * Same Price/Type // * Same Time/Price // * Same Time/Type // * Same Time/Type/Price // - Added minimum/maximum trade volume filtering. // - Added study-owned volume text labels with proper hide/show. // - Preserved historical rebuild and live T&S continuation. // - Preserved original subgraph bubble rendering. // ============================================================================ #include "sierrachart.h" #include #include SCDLLName("Atlas Large Trades TAS") const int ATLAS_MAX_MARKERS_PER_BAR = 60; const int ATLAS_BASE_DRAWING_ID = 87445000; const int PKEY_LAST_SEQUENCE = 1; const int PKEY_AGG_PRICE = 2; const int PKEY_AGG_BAR = 3; const int PKEY_AGG_VOLUME = 4; const int PKEY_AGG_SIDE = 5; const int PKEY_MARKER_INDEX = 6; const int PKEY_EVENTS_VECTOR = 7; const int PKEY_HIST_BUILT = 8; const int PKEY_AGG_DATETIME = 9; struct AtlasLargeTradeEventV3 { int BarIndex; int MarkerIndex; SCDateTimeMS DateTime; float Price; int Volume; int Side; }; inline int AtlasClampIntV3(int Value, int MinValue, int MaxValue) { if (Value < MinValue) return MinValue; if (Value > MaxValue) return MaxValue; return Value; } inline float AtlasGetMarkerSizeV3(int Volume, int Threshold, int MinSize, int MaxSize) { if (Threshold <= 0) return (float)MinSize; const float Ratio = (float)Volume / (float)Threshold; float Size = (float)MinSize * powf(Ratio, 1.55f); if (Size < (float)MinSize) Size = (float)MinSize; if (Size > (float)MaxSize) Size = (float)MaxSize; return Size; } inline int AtlasGetDrawingIDV3(int BarIndex, int MarkerIndex) { return ATLAS_BASE_DRAWING_ID + (BarIndex * ATLAS_MAX_MARKERS_PER_BAR * 2) + (MarkerIndex * 2); } inline int AtlasDetermineIntradaySideV3(const s_IntradayRecord& Record) { if (Record.AskVolume > Record.BidVolume) return SC_TS_ASK; if (Record.BidVolume > Record.AskVolume) return SC_TS_BID; return 0; } void AtlasApplyBubbleSubgraphSettingsV3( SCStudyInterfaceRef sc, bool HollowMarker, int Transparency) { sc.SetChartStudyTransparencyLevel( sc.ChartNumber, sc.StudyGraphInstanceID, AtlasClampIntV3(Transparency, 0, 100)); for (int i = 0; i < ATLAS_MAX_MARKERS_PER_BAR; ++i) { SCString Name; Name.Format("BubbleSlot%d", i); sc.Subgraph[i].Name = Name; sc.Subgraph[i].DrawStyle = HollowMarker ? DRAWSTYLE_CIRCLE_HOLLOW_VARIABLE_SIZE : DRAWSTYLE_TRANSPARENT_CIRCLE_VARIABLE_SIZE; sc.Subgraph[i].PrimaryColor = COLOR_WHITE; sc.Subgraph[i].DrawZeros = false; } } void AtlasClearBarBubbleSlotsV3( SCStudyInterfaceRef sc, int BarIndex) { if (BarIndex < 0 || BarIndex >= sc.ArraySize) return; for (int i = 0; i < ATLAS_MAX_MARKERS_PER_BAR; ++i) { sc.Subgraph[i].Data[BarIndex] = 0.0f; sc.Subgraph[i].Arrays[0][BarIndex] = 0.0f; sc.Subgraph[i].DataColor[BarIndex] = 0; sc.DeleteACSChartDrawing( sc.ChartNumber, TOOL_DELETE_CHARTDRAWING, AtlasGetDrawingIDV3(BarIndex, i) + 1); } } void AtlasDrawLargeTradeMarkerV3( SCStudyInterfaceRef sc, int MarkerIndex, int BarIndex, float Price, int Volume, COLORREF MarkerColor, bool HollowMarker, bool ShowVolumeLabel, float MarkerSize, int Transparency, int LabelFontSize, COLORREF LabelColor) { if (BarIndex < 0 || BarIndex >= sc.ArraySize) return; if (MarkerIndex < 0) return; if (MarkerIndex >= ATLAS_MAX_MARKERS_PER_BAR) MarkerIndex %= ATLAS_MAX_MARKERS_PER_BAR; sc.Subgraph[MarkerIndex].Data[BarIndex] = Price; sc.Subgraph[MarkerIndex].Arrays[0][BarIndex] = MarkerSize; sc.Subgraph[MarkerIndex].DataColor[BarIndex] = MarkerColor; sc.Subgraph[MarkerIndex].DrawStyle = HollowMarker ? DRAWSTYLE_CIRCLE_HOLLOW_VARIABLE_SIZE : DRAWSTYLE_TRANSPARENT_CIRCLE_VARIABLE_SIZE; sc.SetChartStudyTransparencyLevel( sc.ChartNumber, sc.StudyGraphInstanceID, AtlasClampIntV3(Transparency, 0, 100)); if (!ShowVolumeLabel) return; s_UseTool Tool; Tool.Clear(); Tool.ChartNumber = sc.ChartNumber; Tool.Region = sc.GraphRegion; Tool.AddMethod = UTAM_ADD_OR_ADJUST; Tool.AssociatedStudyID = sc.StudyGraphInstanceID; Tool.HideDrawing = sc.HideStudy; Tool.LineNumber = AtlasGetDrawingIDV3(BarIndex, MarkerIndex) + 1; Tool.DrawingType = DRAWING_TEXT; Tool.BeginDateTime = sc.BaseDateTimeIn[BarIndex]; Tool.BeginIndex = BarIndex; Tool.BeginValue = Price; Tool.Color = LabelColor; Tool.FontSize = LabelFontSize; Tool.FontBold = 1; Tool.TextAlignment = DT_CENTER | DT_VCENTER; Tool.TransparentLabelBackground = 1; SCString Label; Label.Format("%d", Volume); Tool.Text = Label; sc.UseTool(Tool); } void AtlasStoreAndDrawEventV3( SCStudyInterfaceRef sc, std::vector* Events, int& MarkerIndex, int BarIndex, SCDateTimeMS EventDateTime, float Price, int Volume, int Side, int Threshold, int MinSize, int MaxSize, COLORREF BuyColor, COLORREF SellColor, bool HollowMarker, bool ShowVolumeLabel, int Transparency, int LabelFontSize, COLORREF LabelColor) { if (Volume < Threshold) return; if (BarIndex < 0 || BarIndex >= sc.ArraySize) return; ++MarkerIndex; if (MarkerIndex >= ATLAS_MAX_MARKERS_PER_BAR) MarkerIndex = 0; const COLORREF Color = Side == SC_TS_ASK ? BuyColor : SellColor; const float Size = AtlasGetMarkerSizeV3( Volume, Threshold, MinSize, MaxSize); AtlasDrawLargeTradeMarkerV3( sc, MarkerIndex, BarIndex, Price, Volume, Color, HollowMarker, ShowVolumeLabel, Size, Transparency, LabelFontSize, LabelColor); if (Events != NULL) { AtlasLargeTradeEventV3 Event; Event.BarIndex = BarIndex; Event.MarkerIndex = MarkerIndex; Event.DateTime = EventDateTime; Event.Price = Price; Event.Volume = Volume; Event.Side = Side; Events->push_back(Event); } } inline bool AtlasShouldMergeV3( int CombineMode, SCDateTimeMS CurrentDT, SCDateTimeMS AggDT, float Price, float AggPrice, int Side, int AggSide) { switch (CombineMode) { case 0: // Same Price/Type return Price == AggPrice && Side == AggSide; case 1: // Same Time/Price return CurrentDT == AggDT && Price == AggPrice; case 2: // Same Time/Type return CurrentDT == AggDT && Side == AggSide; case 3: // Same Time/Type/Price return CurrentDT == AggDT && Side == AggSide && Price == AggPrice; } return false; } int AtlasRebuildCurrentDateFromIntradayFileV3( SCStudyInterfaceRef sc, std::vector* Events, int& MarkerIndex, int Threshold, int CombineMode, int VolumeMinFilter, int VolumeMaxFilter, int MinSize, int MaxSize, COLORREF BuyColor, COLORREF SellColor, bool HollowMarker, bool ShowVolumeLabel, int Transparency, int LabelFontSize, COLORREF LabelColor) { if (sc.ArraySize <= 0) return 0; if (Events != NULL) Events->clear(); MarkerIndex = -1; const int CurrentDate = sc.BaseDateTimeIn[sc.ArraySize - 1].GetDate(); int StartBar = 0; for (int i = sc.ArraySize - 1; i >= 0; --i) { if (sc.BaseDateTimeIn[i].GetDate() != CurrentDate) { StartBar = i + 1; break; } } for (int i = StartBar; i < sc.ArraySize; ++i) AtlasClearBarBubbleSlotsV3(sc, i); float AggPrice = 0.0f; int AggBar = -1; int AggVolume = 0; int AggSide = 0; SCDateTimeMS AggDateTime = 0; for (int BarIndex = StartBar; BarIndex < sc.ArraySize; ++BarIndex) { s_IntradayRecord Record; int SubIndex = 0; bool FirstRead = true; while (true) { IntradayFileLockActionEnum LockAction = FirstRead ? IFLA_LOCK_READ_HOLD : IFLA_NO_CHANGE; FirstRead = false; if (!sc.ReadIntradayFileRecordForBarIndexAndSubIndex( BarIndex, SubIndex, Record, LockAction)) { break; } ++SubIndex; const int Side = AtlasDetermineIntradaySideV3(Record); if (Side != SC_TS_ASK && Side != SC_TS_BID) continue; const int Volume = (int)Record.TotalVolume; if (Volume < VolumeMinFilter) continue; if (VolumeMaxFilter > 0 && Volume > VolumeMaxFilter) continue; const float Price = (float)Record.Close * sc.RealTimePriceMultiplier; SCDateTimeMS DT = Record.DateTime + sc.TimeScaleAdjustment; const bool Merge = AggVolume > 0 && AggBar == BarIndex && AtlasShouldMergeV3( CombineMode, DT, AggDateTime, Price, AggPrice, Side, AggSide); if (Merge) { AggVolume += Volume; } else { AtlasStoreAndDrawEventV3( sc, Events, MarkerIndex, AggBar, AggDateTime, AggPrice, AggVolume, AggSide, Threshold, MinSize, MaxSize, BuyColor, SellColor, HollowMarker, ShowVolumeLabel, Transparency, LabelFontSize, LabelColor); AggPrice = Price; AggBar = BarIndex; AggVolume = Volume; AggSide = Side; AggDateTime = DT; } } sc.ReadIntradayFileRecordForBarIndexAndSubIndex( -1, -1, Record, IFLA_RELEASE_AFTER_READ); } AtlasStoreAndDrawEventV3( sc, Events, MarkerIndex, AggBar, AggDateTime, AggPrice, AggVolume, AggSide, Threshold, MinSize, MaxSize, BuyColor, SellColor, HollowMarker, ShowVolumeLabel, Transparency, LabelFontSize, LabelColor); return Events != NULL ? (int)Events->size() : 0; } SCSFExport scsf_AtlasLargeTradesProV3(SCStudyInterfaceRef sc) { SCInputRef MinimumVolumeThreshold = sc.Input[0]; SCInputRef CombineMode = sc.Input[1]; SCInputRef VolumeMinFilter = sc.Input[2]; SCInputRef VolumeMaxFilter = sc.Input[3]; SCInputRef BuyColor = sc.Input[4]; SCInputRef SellColor = sc.Input[5]; SCInputRef HollowMarker = sc.Input[6]; SCInputRef Transparency = sc.Input[7]; SCInputRef MinMarkerSize = sc.Input[8]; SCInputRef MaxMarkerSize = sc.Input[9]; SCInputRef ShowVolumeLabel = sc.Input[10]; SCInputRef VolumeLabelColor = sc.Input[11]; SCInputRef VolumeLabelFontSize = sc.Input[12]; SCInputRef EnableAlerts = sc.Input[13]; SCInputRef RebuildCurrentDate = sc.Input[14]; if (sc.SetDefaults) { sc.GraphName = "Atlas Large Trades TAS"; sc.StudyDescription = "Large trade bubbles using Time and Sales combine logic"; sc.AutoLoop = 0; sc.GraphRegion = 0; sc.UpdateAlways = 1; sc.MaintainAdditionalChartDataArrays = 1; MinimumVolumeThreshold.Name = "Minimum Bubble Volume"; MinimumVolumeThreshold.SetInt(75); CombineMode.Name = "Combine Mode"; CombineMode.SetCustomInputStrings( "Same Price/Type;" "Same Time/Price;" "Same Time/Type;" "Same Time/Type/Price"); CombineMode.SetCustomInputIndex(3); VolumeMinFilter.Name = "Trade Volume Filter Minimum"; VolumeMinFilter.SetInt(1); VolumeMaxFilter.Name = "Trade Volume Filter Maximum (0=Disabled)"; VolumeMaxFilter.SetInt(0); BuyColor.Name = "Buy Aggression Bubble Color"; BuyColor.SetColor(RGB(0,215,140)); SellColor.Name = "Sell Aggression Bubble Color"; SellColor.SetColor(RGB(255,75,75)); HollowMarker.Name = "Use Hollow Bubbles"; HollowMarker.SetYesNo(0); Transparency.Name = "Bubble Transparency"; Transparency.SetInt(35); MinMarkerSize.Name = "Minimum Bubble Size"; MinMarkerSize.SetInt(9); MaxMarkerSize.Name = "Maximum Bubble Size"; MaxMarkerSize.SetInt(48); ShowVolumeLabel.Name = "Show Volume Label"; ShowVolumeLabel.SetYesNo(1); VolumeLabelColor.Name = "Volume Label Color"; VolumeLabelColor.SetColor(RGB(255,255,255)); VolumeLabelFontSize.Name = "Volume Label Font Size"; VolumeLabelFontSize.SetInt(9); EnableAlerts.Name = "Enable Alerts"; EnableAlerts.SetYesNo(0); RebuildCurrentDate.Name = "Rebuild Current Chart Date"; RebuildCurrentDate.SetYesNo(1); AtlasApplyBubbleSubgraphSettingsV3( sc, HollowMarker.GetYesNo() != 0, Transparency.GetInt()); return; } std::vector* Events = (std::vector*) sc.GetPersistentPointer(PKEY_EVENTS_VECTOR); if (sc.LastCallToFunction) { if (Events != NULL) { delete Events; sc.SetPersistentPointer(PKEY_EVENTS_VECTOR, NULL); } return; } if (Events == NULL) { Events = new std::vector; sc.SetPersistentPointer(PKEY_EVENTS_VECTOR, Events); } AtlasApplyBubbleSubgraphSettingsV3( sc, HollowMarker.GetYesNo() != 0, Transparency.GetInt()); int64_t& LastSequence = sc.GetPersistentInt64(PKEY_LAST_SEQUENCE); float& AggPrice = sc.GetPersistentFloat(PKEY_AGG_PRICE); int& AggBar = sc.GetPersistentInt(PKEY_AGG_BAR); int& AggVolume = sc.GetPersistentInt(PKEY_AGG_VOLUME); int& AggSide = sc.GetPersistentInt(PKEY_AGG_SIDE); int& MarkerIndex = sc.GetPersistentInt(PKEY_MARKER_INDEX); int& HistoricalBuilt = sc.GetPersistentInt(PKEY_HIST_BUILT); SCDateTimeMS& AggDateTime = sc.GetPersistentSCDateTime(PKEY_AGG_DATETIME); if (sc.IsFullRecalculation) { LastSequence = 0; AggPrice = 0.0f; AggBar = -1; AggVolume = 0; AggSide = 0; MarkerIndex = -1; HistoricalBuilt = 0; AggDateTime = 0; } if (RebuildCurrentDate.GetYesNo() && HistoricalBuilt == 0) { AtlasRebuildCurrentDateFromIntradayFileV3( sc, Events, MarkerIndex, MinimumVolumeThreshold.GetInt(), CombineMode.GetIndex(), VolumeMinFilter.GetInt(), VolumeMaxFilter.GetInt(), MinMarkerSize.GetInt(), MaxMarkerSize.GetInt(), BuyColor.GetColor(), SellColor.GetColor(), HollowMarker.GetYesNo() != 0, ShowVolumeLabel.GetYesNo() != 0, Transparency.GetInt(), VolumeLabelFontSize.GetInt(), VolumeLabelColor.GetColor()); HistoricalBuilt = 1; } c_SCTimeAndSalesArray TimeSales; sc.GetTimeAndSales(TimeSales); const int NumRecords = TimeSales.Size(); if (NumRecords == 0) return; uint32_t PriorSequence = (uint32_t)LastSequence; TimeSales.ValidateAndCorrectPriorSequenceNumber( PriorSequence); const uint32_t StartIndex = TimeSales.GetRecordIndexAtGreaterThanSequenceNumber( PriorSequence); for (uint32_t i = StartIndex; i < (uint32_t)NumRecords; ++i) { const s_TimeAndSales& Record = TimeSales[i]; LastSequence = Record.Sequence; const int Side = Record.Type; if (Side != SC_TS_ASK && Side != SC_TS_BID) continue; const int Volume = (int)Record.Volume; if (Volume < VolumeMinFilter.GetInt()) continue; if (VolumeMaxFilter.GetInt() > 0 && Volume > VolumeMaxFilter.GetInt()) { continue; } const float Price = (float)Record.GetPrice() * sc.RealTimePriceMultiplier; SCDateTimeMS DT = Record.DateTime + sc.TimeScaleAdjustment; int BarIndex = sc.GetContainingIndexForSCDateTime( sc.ChartNumber, DT); if (BarIndex < 0) BarIndex = sc.ArraySize - 1; if (BarIndex >= sc.ArraySize) continue; const bool Merge = AggVolume > 0 && AggBar == BarIndex && AtlasShouldMergeV3( CombineMode.GetIndex(), DT, AggDateTime, Price, AggPrice, Side, AggSide); if (Merge) { AggVolume += Volume; } else { AtlasStoreAndDrawEventV3( sc, Events, MarkerIndex, AggBar, AggDateTime, AggPrice, AggVolume, AggSide, MinimumVolumeThreshold.GetInt(), MinMarkerSize.GetInt(), MaxMarkerSize.GetInt(), BuyColor.GetColor(), SellColor.GetColor(), HollowMarker.GetYesNo() != 0, ShowVolumeLabel.GetYesNo() != 0, Transparency.GetInt(), VolumeLabelFontSize.GetInt(), VolumeLabelColor.GetColor()); if (EnableAlerts.GetYesNo() && AggVolume >= MinimumVolumeThreshold.GetInt()) { SCString Msg; Msg.Format( "Atlas Large Trades TAS: %d @ %.2f", AggVolume, AggPrice); sc.SetAlert(1, Msg); } AggPrice = Price; AggBar = BarIndex; AggVolume = Volume; AggSide = Side; AggDateTime = DT; } } }