Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class UltraProNewsScalperAsync : Robot
{
// =========================================================================
// 1. SESSION & REACTION
// =========================================================================
[Parameter("News Start Time (HH:mm)", Group = "1. Session", DefaultValue = "14:00")]
public string NewsStartTimeStr { get; set; }
[Parameter("News End Time (HH:mm)", Group = "1. Session", DefaultValue = "14:15")]
public string NewsEndTimeStr { get; set; }
// =========================================================================
// 2. TRIGGER & FILTERS
// =========================================================================
[Parameter("Pullback EMA", Group = "2. Triggers & Filters", DefaultValue = 9)]
public int EmaLength { get; set; }
[Parameter("Max Bars to Wait", Group = "2. Triggers & Filters", DefaultValue = 30)]
public int MaxWaitBars { get; set; }
[Parameter("Max Allowed SL (Pips)", Group = "2. Triggers & Filters", DefaultValue = 30.0)]
public double MaxSlPips { get; set; }
[Parameter("Max Spread (Pips)", Group = "2. Triggers & Filters", DefaultValue = 3.0)]
public double MaxSpreadPips { get; set; }
[Parameter("SL Wick Buffer (Pips)", Group = "2. Triggers & Filters", DefaultValue = 1.5)]
public double SlBufferPips { get; set; }
// =========================================================================
// 3. RISK & MANAGEMENT
// =========================================================================
[Parameter("Total Risk Per Trade (%)", Group = "3. Risk & Management", DefaultValue = 1.0)]
public double RiskPercent { get; set; }
[Parameter("Target 1 (R:R)", Group = "3. Risk & Management", DefaultValue = 1.0)]
public double Tp1Rr { get; set; }
[Parameter("Target 1 Exit Size (%)", Group = "3. Risk & Management", DefaultValue = 50, MinValue = 1, MaxValue = 99)]
public int Tp1QtyPct { get; set; }
[Parameter("Target 2 / Runner (R:R)", Group = "3. Risk & Management", DefaultValue = 2.5)]
public double Tp2Rr { get; set; }
[Parameter("Auto-Breakeven Runner?", Group = "3. Risk & Management", DefaultValue = true)]
public bool AutoBreakeven { get; set; }
[Parameter("Trail Runner?", Group = "3. Risk & Management", DefaultValue = true)]
public bool UseTrailingStop { get; set; }
[Parameter("Trailing Stop (Pips)", Group = "3. Risk & Management", DefaultValue = 15.0)]
public double TrailingStopPips { get; set; }
[Parameter("Trailing Step (Pips)", Group = "3. Risk & Management", DefaultValue = 2.0)]
public double TrailingStepPips { get; set; }
// State Variables
private TimeSpan _startTime, _endTime;
private ExponentialMovingAverage _ema;
private double _initOpen, _initHigh, _initLow, _pivotExtreme = double.NaN;
private int _trendDir = 0, _barsSinceNews = 0;
private bool _pullbackValid = false, _wasInNews = false;
private DateTime _sessionStartDateTime;
private const string Tp1Label = "NewsScalp_TP1";
private const string Tp2Label = "NewsScalp_TP2";
private const string BoxName = "NewsBox";
protected override void OnStart()
{
if (!TimeSpan.TryParse(NewsStartTimeStr, out _startTime) || !TimeSpan.TryParse(NewsEndTimeStr, out _endTime))
{
Print("Invalid Time Format. Please use HH:mm");
Stop();
}
_ema = Indicators.ExponentialMovingAverage(Bars.ClosePrices, EmaLength);
_wasInNews = IsInNewsWindow(Server.Time.TimeOfDay);
}
private bool IsInNewsWindow(TimeSpan currentTime)
{
if (_startTime < _endTime)
return currentTime >= _startTime && currentTime < _endTime;
return currentTime >= _startTime || currentTime < _endTime; // Cross-midnight support
}
protected override void OnTick()
{
bool inNews = IsInNewsWindow(Server.Time.TimeOfDay);
ManageBreakevenAndTrailing();
UpdateHUD(inNews);
// 1. Session Start (Box Setup)
if (inNews && !_wasInNews)
{
_initOpen = Bars.OpenPrices.LastValue;
_initHigh = Bars.HighPrices.LastValue;
_initLow = Bars.LowPrices.LastValue;
_trendDir = 0;
_pullbackValid = false;
_barsSinceNews = 0;
_sessionStartDateTime = Bars.OpenTimes.LastValue;
Chart.DrawRectangle(BoxName, _sessionStartDateTime, _initHigh, Bars.OpenTimes.LastValue, _initLow, Color.FromArgb(50, Color.DarkGray));
}
// 2. Session Update (Expand Box vertically/horizontally)
if (inNews)
{
_initHigh = Math.Max(_initHigh, Bars.HighPrices.LastValue);
_initLow = Math.Min(_initLow, Bars.LowPrices.LastValue);
Chart.DrawRectangle(BoxName, _sessionStartDateTime, _initHigh, Bars.OpenTimes.LastValue, _initLow, Color.FromArgb(50, Color.DarkGray));
}
// 3. Session End (Determine Trend & Color Box)
if (!inNews && _wasInNews)
{
double initClose = Bars.ClosePrices.Last(1);
if (initClose > _initOpen)
{
_trendDir = 1;
Chart.DrawRectangle(BoxName, _sessionStartDateTime, _initHigh, Bars.OpenTimes.LastValue, _initLow, Color.FromArgb(50, Color.Teal));
}
else if (initClose < _initOpen)
{
_trendDir = -1;
Chart.DrawRectangle(BoxName, _sessionStartDateTime, _initHigh, Bars.OpenTimes.LastValue, _initLow, Color.FromArgb(50, Color.Maroon));
}
else _trendDir = 0;
_pivotExtreme = _trendDir == 1 ? Bars.LowPrices.Last(1) : Bars.HighPrices.Last(1);
}
// Continuous Stop-Loss Pivot Tracking
if (!inNews && _trendDir != 0 && !HasActivePosition())
{
if (_trendDir == 1 && Bars.LowPrices.LastValue < _pivotExtreme) _pivotExtreme = Bars.LowPrices.LastValue;
if (_trendDir == -1 && Bars.HighPrices.LastValue > _pivotExtreme) _pivotExtreme = Bars.HighPrices.LastValue;
}
_wasInNews = inNews;
}
protected override void OnBar()
{
if (IsInNewsWindow(Server.Time.TimeOfDay) || _trendDir == 0 || HasActivePosition())
return;
_barsSinceNews++;
if (_barsSinceNews > MaxWaitBars)
{
_trendDir = 0;
_pullbackValid = false;
return;
}
double close1 = Bars.ClosePrices.Last(1);
double ema1 = _ema.Result.Last(1);
double slBuffer = Symbol.PipSize * SlBufferPips;
// LONG SETUP
if (_trendDir == 1)
{
if (close1 < ema1) _pullbackValid = true;
if (_pullbackValid && close1 > ema1)
{
double sl = Math.Round(_pivotExtreme - slBuffer, Symbol.Digits);
ExecuteSplitOrderAsync(TradeType.Buy, sl);
}
}
// SHORT SETUP
if (_trendDir == -1)
{
if (close1 > ema1) _pullbackValid = true;
if (_pullbackValid && close1 < ema1)
{
double sl = Math.Round(_pivotExtreme + slBuffer, Symbol.Digits);
ExecuteSplitOrderAsync(TradeType.Sell, sl);
}
}
}
private void ExecuteSplitOrderAsync(TradeType tradeType, double sl)
{
// Spread Filter
if (Symbol.Spread / Symbol.PipSize > MaxSpreadPips)
{
Print("Entry rejected: Spread too high.");
return;
}
double entryPrice = tradeType == TradeType.Buy ? Symbol.Ask : Symbol.Bid;
double slPips = Math.Abs(entryPrice - sl) / Symbol.PipSize;
if (slPips <= 0 || slPips > MaxSlPips) return;
// Exact Unit Normalization
double riskAmount = Account.Balance * (RiskPercent / 100.0);
double exactVolume = riskAmount / (slPips * Symbol.PipValue);
double totalVolume = Symbol.NormalizeVolumeInUnits(exactVolume, RoundingMode.Down);
if (totalVolume < Symbol.VolumeInUnitsMin) return;
double vol1 = Symbol.NormalizeVolumeInUnits(totalVolume * (Tp1QtyPct / 100.0), RoundingMode.Down);
double vol2 = Symbol.NormalizeVolumeInUnits(totalVolume - vol1, RoundingMode.Down);
if (vol1 > 0 && vol2 > 0)
{
double tp1Offset = slPips * Tp1Rr;
double tp2Offset = slPips * Tp2Rr;
// Concurrent Non-Blocking Execution
ExecuteMarketOrderAsync(tradeType, SymbolName, vol1, Tp1Label, slPips, tp1Offset, (result1) =>
{
if(!result1.IsSuccessful) Print($"TP1 Order Failed: {result1.Error}");
});
ExecuteMarketOrderAsync(tradeType, SymbolName, vol2, Tp2Label, slPips, tp2Offset, (result2) =>
{
if(!result2.IsSuccessful) Print($"TP2 Order Failed: {result2.Error}");
});
_trendDir = 0;
_pullbackValid = false;
}
}
private void ManageBreakevenAndTrailing()
{
var tp1Exists = Positions.FindAll(Tp1Label, SymbolName).Any();
var runners = Positions.FindAll(Tp2Label, SymbolName);
foreach (var runner in runners)
{
// 1. Auto-Breakeven
if (AutoBreakeven && !tp1Exists)
{
if (runner.TradeType == TradeType.Buy && runner.StopLoss < runner.EntryPrice)
ModifyPositionAsync(runner, runner.EntryPrice, runner.TakeProfit);
else if (runner.TradeType == TradeType.Sell && runner.StopLoss > runner.EntryPrice)
ModifyPositionAsync(runner, runner.EntryPrice, runner.TakeProfit);
}
// 2. Trailing Stop
if (UseTrailingStop && !tp1Exists)
{
double trailPips = TrailingStopPips * Symbol.PipSize;
double stepPips = TrailingStepPips * Symbol.PipSize;
if (runner.TradeType == TradeType.Buy && Symbol.Bid - runner.EntryPrice > trailPips)
{
double newSl = Math.Round(Symbol.Bid - trailPips, Symbol.Digits);
if (runner.StopLoss == null || runner.StopLoss < newSl - stepPips)
ModifyPositionAsync(runner, newSl, runner.TakeProfit);
}
else if (runner.TradeType == TradeType.Sell && runner.EntryPrice - Symbol.Ask > trailPips)
{
double newSl = Math.Round(Symbol.Ask + trailPips, Symbol.Digits);
if (runner.StopLoss == null || runner.StopLoss > newSl + stepPips)
ModifyPositionAsync(runner, newSl, runner.TakeProfit);
}
}
}
}
private bool HasActivePosition()
{
return Positions.FindAll(Tp1Label, SymbolName).Any() || Positions.FindAll(Tp2Label, SymbolName).Any();
}
private void UpdateHUD(bool inNews)
{
string bias = inNews ? "Awaiting News..." : (_trendDir == 1 ? "Bullish Pullback" : (_trendDir == -1 ? "Bearish Pullback" : "Flat / No Setup"));
string pos = HasActivePosition() ? "ACTIVE" : "WAITING";
double spread = Math.Round(Symbol.Spread / Symbol.PipSize, 1);
string hudText = $"=== ULTRA SCALPER HUD ===\nStatus: {bias}\nPosition: {pos}\nSpread: {spread} pips";
Chart.DrawStaticText("HUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, Color.White);
}
}
}