Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class CostAwareAsiaBreakout : Robot
{
// =========================================================================
// 1. INPUTS & FILTERS
// =========================================================================
[Parameter("Trade Volume (Units)", Group = "Trade Settings", DefaultValue = 10000, MinValue = 1000)]
public double TradeVolume { get; set; }
[Parameter("Asia Start Hour (UTC)", Group = "Session Settings", DefaultValue = 0, MinValue = 0, MaxValue = 23)]
public int AsiaStartHour { get; set; }
[Parameter("Asia End Hour (UTC)", Group = "Session Settings", DefaultValue = 6, MinValue = 0, MaxValue = 23)]
public int AsiaEndHour { get; set; }
[Parameter("Expected Cost (Pips)", Group = "Cost & Risk Mechanics", DefaultValue = 1.5)]
public double MaxCostPips { get; set; }
[Parameter("Min Target/Cost Margin", Group = "Cost & Risk Mechanics", DefaultValue = 3.0)]
public double MinCostMargin { get; set; }
[Parameter("Risk/Reward Ratio", Group = "Cost & Risk Mechanics", DefaultValue = 1.5)]
public double RiskReward { get; set; }
[Parameter("Skip if Spread Exceeds (Pips)", Group = "Cost & Risk Mechanics", DefaultValue = 2.0)]
public double MaxSpreadCheck { get; set; }
[Parameter("RVOL Length", Group = "Volume & Momentum (RVOL)", DefaultValue = 20, MinValue = 1)]
public int RvolLength { get; set; }
[Parameter("Minimum RVOL", Group = "Volume & Momentum (RVOL)", DefaultValue = 1.5)]
public double RvolThreshold { get; set; }
// =========================================================================
// 2. STATE VARIABLES
// =========================================================================
private double _asiaHigh = double.NaN;
private double _asiaLow = double.NaN;
private double _asiaMid = double.NaN;
private bool _wasInSession = false;
private readonly string _label = "CostAwareBreakout";
// =========================================================================
// 3. EVENT: ON TICK (Manages Session State & Invalidations)
// =========================================================================
protected override void OnTick()
{
bool inSession = IsInSession(Server.Time);
bool sessionStart = inSession && !_wasInSession;
// Handle New Session Reset
if (sessionStart)
{
ResetSession();
}
// Track Highest High and Lowest Low during Asia Session
if (inSession)
{
double currentHigh = Bars.HighPrices.Last(0);
double currentLow = Bars.LowPrices.Last(0);
if (double.IsNaN(_asiaHigh) || currentHigh > _asiaHigh) _asiaHigh = currentHigh;
if (double.IsNaN(_asiaLow) || currentLow < _asiaLow) _asiaLow = currentLow;
_asiaMid = (_asiaHigh + _asiaLow) / 2;
// Draw levels visually on the chart
Chart.DrawHorizontalLine("AsiaHigh", _asiaHigh, Color.FromArgb(100, Color.Green));
Chart.DrawHorizontalLine("AsiaLow", _asiaLow, Color.FromArgb(100, Color.Red));
Chart.DrawHorizontalLine("AsiaMid", _asiaMid, Color.FromArgb(100, Color.Gray));
}
else
{
// Invalidation Check: If waiting on a limit order and price hits the mid-range
foreach (var order in PendingOrders.Where(o => o.Label == _label))
{
if (order.TradeType == TradeType.Buy && Symbol.Bid < _asiaMid)
{
CancelPendingOrder(order);
Print("Long limit order canceled: Price drifted back to structural invalidation (Asia Mid).");
}
else if (order.TradeType == TradeType.Sell && Symbol.Ask > _asiaMid)
{
CancelPendingOrder(order);
Print("Short limit order canceled: Price drifted back to structural invalidation (Asia Mid).");
}
}
}
_wasInSession = inSession;
}
// =========================================================================
// 4. EVENT: ON BAR (Manages Breakouts & Order Placement)
// =========================================================================
protected override void OnBar()
{
bool inSession = IsInSession(Server.Time);
// Only trade outside the session window, and only if range was established
if (inSession || double.IsNaN(_asiaHigh)) return;
// RVOL Calculation mapping Tick Volumes
double avgVol = 0;
for (int i = 1; i <= RvolLength; i++)
{
// Last(1) is the candle that just closed
avgVol += Bars.TickVolumes.Last(i);
}
avgVol /= RvolLength;
double currentVol = Bars.TickVolumes.Last(1);
double currentRvol = currentVol / avgVol;
double closeCurrent = Bars.ClosePrices.Last(1);
double closePrevious = Bars.ClosePrices.Last(2);
// Breakout Identifiers
bool breakoutLong = closeCurrent > _asiaHigh && closePrevious <= _asiaHigh && currentRvol >= RvolThreshold;
bool breakoutShort = closeCurrent < _asiaLow && closePrevious >= _asiaLow && currentRvol >= RvolThreshold;
if (breakoutLong || breakoutShort)
{
// Live Spread filter
double liveSpread = Symbol.Spread / Symbol.PipSize;
if (liveSpread > MaxSpreadCheck)
{
Print("Breakout ignored: Spread too high ({0} pips).", Math.Round(liveSpread, 1));
return;
}
if (breakoutLong)
{
double riskPips = (_asiaHigh - _asiaMid) / Symbol.PipSize;
double targetPips = riskPips * RiskReward;
if (targetPips >= MaxCostPips * MinCostMargin)
{
PlaceLimitOrder(TradeType.Buy, SymbolName, TradeVolume, _asiaHigh, _label, riskPips, targetPips);
Print("Long structural break validated. Limit placed at {0}, RVOL: {1}x", _asiaHigh, Math.Round(currentRvol, 2));
}
else
{
Print("Long breakout skipped: Required target ({0} pips) doesn't clear the cost margin requirement.", Math.Round(targetPips, 1));
}
}
if (breakoutShort)
{
double riskPips = (_asiaMid - _asiaLow) / Symbol.PipSize;
double targetPips = riskPips * RiskReward;
if (targetPips >= MaxCostPips * MinCostMargin)
{
PlaceLimitOrder(TradeType.Sell, SymbolName, TradeVolume, _asiaLow, _label, riskPips, targetPips);
Print("Short structural break validated. Limit placed at {0}, RVOL: {1}x", _asiaLow, Math.Round(currentRvol, 2));
}
else
{
Print("Short breakout skipped: Required target ({0} pips) doesn't clear the cost margin requirement.", Math.Round(targetPips, 1));
}
}
}
}
// =========================================================================
// 5. HELPER METHODS
// =========================================================================
private bool IsInSession(DateTime time)
{
if (AsiaStartHour < AsiaEndHour)
return time.Hour >= AsiaStartHour && time.Hour < AsiaEndHour;
else // Handles crossing midnight
return time.Hour >= AsiaStartHour || time.Hour < AsiaEndHour;
}
private void ResetSession()
{
_asiaHigh = double.NaN;
_asiaLow = double.NaN;
_asiaMid = double.NaN;
// Wipe out pending limit orders that never filled
foreach (var order in PendingOrders.Where(o => o.Label == _label))
{
CancelPendingOrder(order);
}
// Close any open trades to reset cleanly for the next session
foreach (var position in Positions.Where(p => p.Label == _label))
{
ClosePosition(position);
}
Chart.RemoveObject("AsiaHigh");
Chart.RemoveObject("AsiaLow");
Chart.RemoveObject("AsiaMid");
Print("New Asia Session started: Range cleared, stale orders and positions wiped.");
}
}
}