227 lines
8.0 KiB
C#
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;
|
|
}
|
|
}
|
|
} |