Re: Aggregating Liquidity: How to Scale Into Sweeps Using Cost Averaging
Posted: Mon Oct 05, 2026 8:08 am
The Pro C# cBot Code
Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.UI;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class LiquidityAggregatorPro : Robot
{
[Parameter("Trade Direction", Group = "Core Execution", DefaultValue = TradeType.Buy)]
public TradeType Direction { get; set; }
[Parameter("Bot Label", Group = "Core Execution", DefaultValue = "LiqAggregator_Pro")]
public string BotLabel { get; set; }
[Parameter("Initial Volume (Units)", Group = "Sizing", DefaultValue = 10000, MinValue = 1000, Step = 1000)]
public double InitialVolume { get; set; }
[Parameter("Volume Multiplier", Group = "Sizing", DefaultValue = 1.5, MinValue = 1.0, Step = 0.1)]
public double VolumeMultiplier { get; set; }
[Parameter("Max Tiers", Group = "Sizing", DefaultValue = 5, MinValue = 1)]
public int MaxTiers { get; set; }
[Parameter("Scale-In Distance (Pips)", Group = "Grid Parameters", DefaultValue = 10.0)]
public double DistancePips { get; set; }
[Parameter("Target Net Profit (Pips)", Group = "Grid Parameters", DefaultValue = 5.0)]
public double TakeProfitPips { get; set; }
[Parameter("Max Spread (Pips)", Group = "Safety", DefaultValue = 2.0)]
public double MaxSpreadPips { get; set; }
[Parameter("Max Slippage (Pips)", Group = "Safety", DefaultValue = 1.0)]
public double MaxSlippage { get; set; }
// State variables
private bool _isActive = false;
private double _breakEvenPrice = 0;
private double _targetPrice = 0;
// UI Elements
private TextBlock _uiTiers;
private TextBlock _uiVolume;
private TextBlock _uiBreakEven;
private TextBlock _uiTarget;
private TextBlock _uiPnL;
private TextBlock _uiStatus;
protected override void OnStart()
{
DrawHUD();
// Subscribe to position events
Positions.Closed += OnPositionsClosed;
// Execute initial tier asynchronously
_uiStatus.Text = "Executing Tier 1...";
_uiStatus.ForegroundColor = Color.Orange;
ExecuteMarketOrderAsync(Direction, SymbolName, InitialVolume, BotLabel, MaxSlippage, null, null, "Tier 1", OnOrderExecuted);
}
protected override void OnTick()
{
UpdateHUD();
if (!_isActive) return;
var positions = Positions.FindAll(BotLabel, SymbolName, Direction);
if (positions.Length == 0 || positions.Length >= MaxTiers) return;
// Check Spread Safety
double currentSpread = Symbol.Spread / Symbol.PipSize;
if (currentSpread > MaxSpreadPips)
{
_uiStatus.Text = $"Spread too high ({Math.Round(currentSpread, 1)} pips)";
_uiStatus.ForegroundColor = Color.Red;
return; // Wait for spread to normalize
}
_uiStatus.Text = "Monitoring Excursion...";
_uiStatus.ForegroundColor = Color.LightGreen;
// Calculate excursion
double lastPrice = Direction == TradeType.Buy
? positions.Min(p => p.EntryPrice)
: positions.Max(p => p.EntryPrice);
bool shouldScale = Direction == TradeType.Buy
? Symbol.Ask <= lastPrice - (DistancePips * Symbol.PipSize)
: Symbol.Bid >= lastPrice + (DistancePips * Symbol.PipSize);
if (shouldScale)
{
var lastPosition = positions.OrderBy(p => p.EntryTime).Last();
double newVolume = Symbol.NormalizeVolumeInUnits(lastPosition.VolumeInUnits * VolumeMultiplier);
int nextTier = positions.Length + 1;
ExecuteMarketOrderAsync(Direction, SymbolName, newVolume, BotLabel, MaxSlippage, null, null, $"Tier {nextTier}", OnOrderExecuted);
}
}
private void OnOrderExecuted(TradeResult result)
{
if (result.IsSuccessful)
{
_isActive = true;
UpdateTakeProfitAsync();
}
else
{
Print($"Execution Failed: {result.Error}");
}
}
private void UpdateTakeProfitAsync()
{
var positions = Positions.FindAll(BotLabel, SymbolName, Direction);
if (positions.Length == 0) return;
double totalVolume = positions.Sum(p => p.VolumeInUnits);
double weightedPriceSum = positions.Sum(p => p.EntryPrice * p.VolumeInUnits);
_breakEvenPrice = weightedPriceSum / totalVolume;
_targetPrice = Direction == TradeType.Buy
? _breakEvenPrice + (TakeProfitPips * Symbol.PipSize)
: _breakEvenPrice - (TakeProfitPips * Symbol.PipSize);
_targetPrice = Math.Round(_targetPrice, Symbol.Digits);
// Asynchronously update TP for all positions to avoid thread blocking
foreach (var pos in positions)
{
if (pos.TakeProfit != _targetPrice)
{
ModifyPositionAsync(pos, pos.StopLoss, _targetPrice);
}
}
}
private void OnPositionsClosed(PositionClosedEventArgs args)
{
if (args.Position.Label != BotLabel || args.Position.SymbolName != SymbolName) return;
var remainingPositions = Positions.FindAll(BotLabel, SymbolName, Direction);
// If all positions in our basket are closed, shut down the bot
if (remainingPositions.Length == 0 && _isActive)
{
Print("Basket target hit or closed manually. Terminating cBot.");
Stop();
}
else
{
// A partial close happened (e.g. manual intervention), recalculate TP
UpdateTakeProfitAsync();
}
}
#region UI Rendering and Updates
private void DrawHUD()
{
var border = new Border
{
BackgroundColor = Color.FromArgb(200, 20, 20, 20),
BorderColor = Color.FromArgb(255, 60, 60, 60),
BorderThickness = 1,
CornerRadius = 5,
Margin = new Thickness(10),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
Padding = new Thickness(10)
};
var stackPanel = new StackPanel { Orientation = Orientation.Vertical };
// Title
stackPanel.AddChild(new TextBlock { Text = "LIQUIDITY AGGREGATOR", FontWeight = FontWeight.Bold, ForegroundColor = Color.White, Margin = new Thickness(0, 0, 0, 10) });
// Create UI elements
_uiTiers = CreateUIRow(stackPanel, "Active Tiers:");
_uiVolume = CreateUIRow(stackPanel, "Total Volume:");
_uiBreakEven = CreateUIRow(stackPanel, "Break Even:");
_uiTarget = CreateUIRow(stackPanel, "Target Price:");
_uiPnL = CreateUIRow(stackPanel, "Current PnL:");
_uiStatus = new TextBlock { Text = "Initializing...", ForegroundColor = Color.Gray, Margin = new Thickness(0, 10, 0, 0), FontStyle = FontStyle.Italic };
stackPanel.AddChild(_uiStatus);
border.Child = stackPanel;
Chart.AddControl(border);
}
private TextBlock CreateUIRow(StackPanel parent, string labelText)
{
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 2, 0, 2) };
row.AddChild(new TextBlock { Text = labelText, Width = 90, ForegroundColor = Color.Gray });
var valueText = new TextBlock { Text = "-", ForegroundColor = Color.White };
row.AddChild(valueText);
parent.AddChild(row);
return valueText;
}
private void UpdateHUD()
{
var positions = Positions.FindAll(BotLabel, SymbolName, Direction);
_uiTiers.Text = $"{positions.Length} / {MaxTiers}";
if (positions.Length > 0)
{
double totalVol = positions.Sum(p => p.VolumeInUnits);
double netProfit = positions.Sum(p => p.NetProfit);
_uiVolume.Text = (totalVol / 100000).ToString("0.00") + " Lots"; // Assumes standard lot size
_uiBreakEven.Text = _breakEvenPrice.ToString($"F{Symbol.Digits}");
_uiTarget.Text = _targetPrice.ToString($"F{Symbol.Digits}");
_uiPnL.Text = netProfit >= 0 ? $"+${netProfit:F2}" : $"-${Math.Abs(netProfit):F2}";
_uiPnL.ForegroundColor = netProfit >= 0 ? Color.LimeGreen : Color.Tomato;
}
else
{
_uiVolume.Text = "-";
_uiBreakEven.Text = "-";
_uiTarget.Text = "-";
_uiPnL.Text = "-";
_uiPnL.ForegroundColor = Color.White;
}
}
#endregion
}
}