Files
cTrader-Algo/Sources/Robots/HmaStructureBot/HmaStructureBot/HmaStructureBot.cs
T
Michael Schimmel a1d2e96f8a Initial commit
2026-01-28 09:10:52 +01:00

227 lines
8.0 KiB
C#

using System;
using System.Collections.Generic;
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 HmaStructureBot : Robot
{
// --- Parameters ---
[Parameter("HMA Period", DefaultValue = 25)]
public int HmaPeriod { get; set; }
[Parameter("Risk % per Trade", DefaultValue = 1.0, MinValue = 0.01)]
public double RiskPercent { get; set; }
[Parameter("Order Label", DefaultValue = "HMA_Struct")]
public string OrderLabel { get; set; }
// Expiration removed
// --- Indicators ---
private HullMovingAverage _hma;
// --- State Management ---
private DateTime _lastSetupTime;
// --- Data Structures ---
private enum ExtremumType { High, Low }
private struct Extremum
{
public ExtremumType Type;
public double Value;
public int Index; // Index relative to logic
public DateTime Time; // Unique identifier for the extremum
}
protected override void OnStart()
{
_hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod);
}
protected override void OnBarClosed()
{
// 1. Manage existing pending orders
ManageExistingOrders();
// 2. Find Extrema
var extrema = GetExtrema(200, 5);
if (extrema.Count < 5)
return;
// 3. Check for duplicate execution on the same setup
// The setup is defined by the latest extremum (extrema[0])
if (extrema[0].Time == _lastSetupTime)
return;
bool orderPlaced = false;
// --- Short Logic ---
// Last extremum must be a Trough (T0)
if (extrema[0].Type == ExtremumType.Low)
{
var t0 = extrema[0].Value;
var h0 = extrema[1].Value;
var t1 = extrema[2].Value;
var h1 = extrema[3].Value;
var t2 = extrema[4].Value;
// Condition: T1 < T2 and H0 < T2
if (t1 < t2 && h0 < t2)
{
double entryPrice = 0.5 * (t2 + h0);
double stopLossPrice = h1;
double takeProfitPrice = Math.Max(t0, t1);
PlaceStructureOrder(TradeType.Sell, entryPrice, stopLossPrice, takeProfitPrice);
orderPlaced = true;
}
}
// --- Long Logic ---
// Last extremum must be a Peak (H0)
else if (extrema[0].Type == ExtremumType.High)
{
var h0 = extrema[0].Value;
var t0 = extrema[1].Value;
var h1 = extrema[2].Value;
var t1 = extrema[3].Value;
var h2 = extrema[4].Value;
// Condition: H1 > H2 and T0 > H2
if (h1 > h2 && t0 > h2)
{
double entryPrice = 0.5 * (h2 + t0);
double stopLossPrice = t1;
double takeProfitPrice = Math.Min(h0, h1);
PlaceStructureOrder(TradeType.Buy, entryPrice, stopLossPrice, takeProfitPrice);
orderPlaced = true;
}
}
// 4. Mark Setup as handled if we placed an order
if (orderPlaced)
{
_lastSetupTime = extrema[0].Time;
}
}
private void PlaceStructureOrder(TradeType type, double entryPrice, double slPrice, double tpPrice)
{
// Calculate Risk
double riskAmount = Account.Balance * (RiskPercent / 100.0);
// Note: VolumeForFixedRisk still requires SL in Pips calculation
double slDistancePips = Math.Abs(entryPrice - slPrice) / Symbol.PipSize;
if (slDistancePips <= 0) return;
double volume = Symbol.VolumeForFixedRisk(riskAmount, slDistancePips);
volume = Symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down);
// Place Limit Order using Absolute Prices
// Expiry is null (GTC - Good Till Cancelled)
PlaceLimitOrder(type, SymbolName, volume, entryPrice, OrderLabel, slPrice, tpPrice, ProtectionType.Absolute, null);
}
private void ManageExistingOrders()
{
foreach (var order in PendingOrders.ToArray())
{
if (order.Label != OrderLabel || order.SymbolName != SymbolName)
continue;
var barLow = Bars.LowPrices.Last(0);
var barHigh = Bars.HighPrices.Last(0);
bool isCancelled = false;
// --- Check SL Hit ---
if (order.StopLoss.HasValue)
{
if (order.TradeType == TradeType.Buy)
{
if (barLow <= order.StopLoss.Value)
{
order.Cancel();
isCancelled = true;
}
}
else // Sell
{
if (barHigh >= order.StopLoss.Value)
{
order.Cancel();
isCancelled = true;
}
}
}
if (isCancelled) continue;
// --- Check TP Extension ---
if (order.TakeProfit.HasValue)
{
if (order.TradeType == TradeType.Sell)
{
// If price went lower than TP, move TP to Low
if (barLow < order.TakeProfit.Value)
{
ModifyPendingOrder(order, order.TargetPrice, order.StopLoss, barLow, order.ProtectionType);
}
}
else // Buy
{
// If price went higher than TP, move TP to High
if (barHigh > order.TakeProfit.Value)
{
ModifyPendingOrder(order, order.TargetPrice, order.StopLoss, barHigh, order.ProtectionType);
}
}
}
}
}
private List<Extremum> GetExtrema(int searchDepth, int minCount)
{
var list = new List<Extremum>();
var hma = _hma.Result;
// i starts at 1 to compare with confirmed i+1 and i-1 (where i-1 is closer to closed bar Last(0))
for (int i = 1; i < searchDepth && i < hma.Count - 1; i++)
{
double current = hma.Last(i);
double prev = hma.Last(i + 1); // Older
double next = hma.Last(i - 1); // Newer
if (current > prev && current > next)
{
list.Add(new Extremum {
Type = ExtremumType.High,
Value = current,
Index = i,
Time = Bars.OpenTimes.Last(i)
});
}
else if (current < prev && current < next)
{
list.Add(new Extremum {
Type = ExtremumType.Low,
Value = current,
Index = i,
Time = Bars.OpenTimes.Last(i)
});
}
if (list.Count >= minCount)
break;
}
return list;
}
}
}