Tarea técnica
Hello
I've written an c# program for CTrader which watches for buy / sell stop orders. If it detects one the bot calculates the risk based on a eur amount I enter. The TP ratio is adjustable.
E.g.. i place a pending order (doesnt matter at which price) with an SL. the bots immediately cancels it and makes a market order with the tp at the tp ratio and the SL and the providet position. Crucial: the lotsize is calculated based on the entered risk amount in the EA settings. Thats all, as simple as that.
what my problem is: It works only for forex. on gold and indices chatgpt didn't manage to code it even after hours. idk if my broker fusion is special but I can trade 0,01 NAS100 so it should work. Your job is to modify the bot so it runs perfectly on all stocks, indices, metals. And to add an input in which the risk is selectable as a percentage of the balance. Thats all.
to be considered done the TP / SL manager must work on all symbols including: BTC, Forex, Metals, Indices and Stocks and the Risk Percentage must be added. It should just behave like you would expect, nothing fancy.
Here is the code so far:
```
using cAlgo.API;
using cAlgo.API.Internals;
using System;
using System.Collections.Generic;
namespace cAlgo.Robots
{
[Robot(AccessRights = AccessRights.None)]
public class UniversalRiskPlacer : Robot
{
// -------- Benutzer-Parameter --------
[Parameter("Risk (Account Ccy)", DefaultValue = 10.0, MinValue = 0.01, Step = 0.01)]
public double RiskCurrency { get; set; }
[Parameter("R (TP = R × SL)", DefaultValue = 5.00, MinValue = 0.5, Step = 0.01)]
public double RMultiple { get; set; }
[Parameter("Min Distance (pips)", DefaultValue = 5.0, MinValue = 0.0, Step = 0.1)]
public double MinDistancePips { get; set; }
[Parameter("Trigger: Pending Stop+SL", DefaultValue = true)]
public bool TriggerOnPendingStopWithSL { get; set; }
[Parameter("Require SL on Pending", DefaultValue = true)]
public bool RequirePendingHasSL { get; set; }
// --- Auto-Erkennung / Overrides (nur falls nötig) ---
[Parameter("Auto Detect LotStep/MinLot", DefaultValue = true)]
public bool AutoDetectLotStep { get; set; }
[Parameter("Override MinLot (0 = auto)", DefaultValue = 0.00, MinValue = 0.0, Step = 0.001)]
public double MinLotOverride { get; set; }
[Parameter("Override LotStep (0 = auto)", DefaultValue = 0.00, MinValue = 0.0, Step = 0.001)]
public double LotStepOverride { get; set; }
[Parameter("Force Min Size if Below Risk", DefaultValue = false)]
public bool ForceMinIfBelowRisk { get; set; }
[Parameter("Dry Run (no orders)", DefaultValue = false)]
public bool DryRun { get; set; }
[Parameter("Debug Logs", DefaultValue = true)]
public bool DebugLogs { get; set; }
[Parameter("Debug Overlay", DefaultValue = true)]
public bool DebugOverlay { get; set; }
// -------- intern --------
private readonly HashSet<long> _seen = new HashSet<long>();
private readonly List<string> _ol = new List<string>();
protected override void OnStart()
{
Timer.Start(1);
Log($"[START] Risk={RiskCurrency} | R={RMultiple} | MinDist≥{MinDistancePips}p | AutoDetect={AutoDetectLotStep} | ForceMin={ForceMinIfBelowRisk} | DryRun={DryRun}");
}
protected override void OnTimer()
{
if (!TriggerOnPendingStopWithSL) return;
// 1) Kandidaten einsammeln
foreach (var po in PendingOrders)
{
if (_seen.Contains(po.Id)) continue;
var pass = IsCandidate(po, out string reason);
if (!pass)
{
Log($"[GATE-1 SKIP] {po.SymbolName} #{po.Id}: {reason}");
continue;
}
_seen.Add(po.Id);
try
{
Handle(po);
}
catch (Exception ex)
{
Log($"[ERR] Handle {po.SymbolName} #{po.Id}: {ex.Message}");
}
}
}
// ---------- Logic Gate 1: Kandidat? ----------
private bool IsCandidate(PendingOrder po, out string reason)
{
reason = "";
if (!TriggerOnPendingStopWithSL)
{
reason = "Trigger disabled";
return false;
}
if (po.OrderType != PendingOrderType.Stop)
{
reason = $"OrderType={po.OrderType} (need Stop)";
return false;
}
if (RequirePendingHasSL && !po.StopLoss.HasValue)
{
reason = "No SL on pending";
return false;
}
var s = Symbols.GetSymbol(po.SymbolName);
if (s == null)
{
reason = "Symbol null";
return false;
}
double mkt = (po.TradeType == TradeType.Buy) ? s.Ask : s.Bid;
double dPip = Math.Abs(mkt - po.TargetPrice) / s.PipSize;
if (dPip < MinDistancePips)
{
reason = $"Dist {dPip:F2}p < MinDistance {MinDistancePips:F2}p";
return false;
}
Log($"[GATE-1 OK] {po.SymbolName} #{po.Id}: dist={dPip:F2}p, SL={po.StopLoss.Value}");
return true;
}
// ---------- Haupt-Handler mit mehrstufigen Gates ----------
private void Handle(PendingOrder po)
{
var s = Symbols.GetSymbol(po.SymbolName);
var side = po.TradeType;
double sl = po.StopLoss.Value;
double entryMkt = (side == TradeType.Buy) ? s.Ask : s.Bid;
// Gate 2: Symbolprofil
var prof = BuildSymbolProfile(s);
if (!prof.Valid)
{
Log($"[GATE-2 SKIP] {s.Name}: invalid pip/lot profile ({prof.Warn})");
return;
}
Log($"[PROFILE] {s.Name} | units/lot≈{prof.UnitsPerLot:F4} | lotStep≈{prof.LotStep:F4} | minLot≈{prof.MinLot:F4} | pipVal/lot≈{prof.PipValuePerLot:F4} | uMin={prof.MinUnits} | uStep={prof.UnitStepBroker}");
// Gate 3: Risiko-Mathe in LOTS (über VolumeForFixedRisk/AmountRisked)
var risk = ComputeLots(s, entryMkt, sl, RiskCurrency);
if (!risk.Ok)
{
Log($"[GATE-3 SKIP] {s.Name}: {risk.Msg}");
return;
}
Log($"[RISK] pipDist={risk.PipDist:F2}p | lossPerLot={risk.LossPerLot:F4} | lotsRaw={risk.LotsRaw:F5}");
// Gate 4: Lot-Rundung auf LotStep + MinLot
double lotStep = (LotStepOverride > 0 && !AutoDetectLotStep) ? LotStepOverride : prof.LotStep;
double minLot = (MinLotOverride > 0 && !AutoDetectLotStep) ? MinLotOverride : prof.MinLot;
if (!(lotStep > 0)) lotStep = 0.01; // failsafe
if (!(minLot > 0)) minLot = lotStep; // failsafe
double lotsRounded = Math.Floor(risk.LotsRaw / lotStep) * lotStep;
double minRiskNeed = minLot * risk.LossPerLot; // € nötig für MinLot
Log($"[ROUND] lotStep={lotStep:F4} | minLot={minLot:F4} | lotsRounded={lotsRounded:F5} | MinRiskNeeded={minRiskNeed:F2}");
if (lotsRounded < minLot)
{
if (!ForceMinIfBelowRisk)
{
Log($"[GATE-4 SKIP] {s.Name}: risk {RiskCurrency:F2} < {minRiskNeed:F2} for MinLot={minLot}");
return;
}
lotsRounded = minLot;
Log($"[WARN] Forcing MinLot={minLot:F4} → tatsächliches Risiko ≈ {minRiskNeed:F2}.");
}
// Gate 5: Lots → Units, Step aus Profil
long units = LotsToUnits(prof, lotsRounded);
if (units <= 0)
{
Log($"[GATE-5 SKIP] {s.Name}: units<=0 nach Konvertierung (lots={lotsRounded:F5})");
return;
}
Log($"[UNITS] lots={lotsRounded:F5} → units={units} | unitStep={prof.UnitStep:F0} | brokerMinU={prof.MinUnits}, brokerStepU={prof.UnitStepBroker}");
// Gate 6: Broker-Min/Max clamp + Sanity-Lücke (Lot-Backcheck)
if (prof.MinUnits > 0 && units < prof.MinUnits) units = prof.MinUnits;
if (prof.MaxUnits > 0 && units > prof.MaxUnits) units = prof.MaxUnits;
double backLots = UnitsToLots(prof, units);
double backLotsDiff = Math.Abs(backLots - lotsRounded);
if (backLotsDiff > lotStep + 1e-9)
{
Log($"[WARN] backLots diff {backLotsDiff:F4} > lotStep {lotStep:F4} (back={backLots:F4})");
}
// Gate 7: Orderausführung
double rawTP = ComputeTP(entryMkt, sl, side, RMultiple);
if (DryRun)
{
Log($"[DRY-RUN] {s.Name} {side}: units={units} | entry≈{entryMkt} | SL={sl} | TP≈{rawTP} | R={RMultiple:F2}");
CancelPendingOrder(po);
return;
}
CancelPendingOrder(po);
var tr = ExecuteMarketOrder(side, s.Name, units, "UNIRISK");
if (tr == null || !tr.IsSuccessful || tr.Position == null)
{
Log($"[FAIL] {s.Name} MarketOrder: {tr?.Error}");
return;
}
var pos = tr.Position;
var (nSL, nTP) = NormalizeSLTP(s, side, pos.EntryPrice, sl, rawTP);
#pragma warning disable CS0618
ModifyPosition(pos, nSL, nTP);
#pragma warning restore CS0618
Log($"[OK] {s.Name} {side} → units={units} | entry={pos.EntryPrice} | SL={nSL} | TP={nTP} | R={RMultiple:F2}");
}
// ---------- Symbolprofil (Auto-Detection + Failsafes) ----------
private struct SymbolProfile
{
public bool Valid;
public string Warn;
public double UnitsPerLot; // Units je 1.00 Lot
public long UnitStep; // abgeleiteter Units-Schritt
public long UnitStepBroker; // gemeldeter Broker Units-Step
public long MinUnits;
public long MaxUnits;
public double LotStep; // Lot-Schritt
public double MinLot; // Mindest-Lot
public double PipValuePerLot; // € pro Pip pro Lot (für Logging)
}
private SymbolProfile BuildSymbolProfile(Symbol s)
{
var p = new SymbolProfile
{
Valid = false,
Warn = ""
};
// Broker-Units-Infos
p.MinUnits = (long)Math.Round(Math.Max(1, s.VolumeInUnitsMin));
p.MaxUnits = (long)Math.Round(Math.Max(p.MinUnits, s.VolumeInUnitsMax));
p.UnitStepBroker = (long)Math.Round(Math.Max(1, s.VolumeInUnitsStep));
// Units pro 1.00 Lot
double unitsPerLot = 0;
try
{
unitsPerLot = s.QuantityToVolumeInUnits(1.0); // 1 Lot → Units
}
catch
{
unitsPerLot = 0;
}
if (!(unitsPerLot > 0))
{
unitsPerLot = s.LotSize > 0 ? s.LotSize : 100.0;
p.Warn += "UnitsPerLot fallback; ";
}
p.UnitsPerLot = unitsPerLot;
// LotStep/MinLot aus Units abgeleitet
double lotStepFromUnits = 0.0;
double minLotFromUnits = 0.0;
try
{
lotStepFromUnits = s.VolumeInUnitsToQuantity(s.VolumeInUnitsStep);
minLotFromUnits = s.VolumeInUnitsToQuantity(s.VolumeInUnitsMin);
}
catch
{
p.Warn += "VolumeInUnitsToQuantity failed; ";
}
if (AutoDetectLotStep)
{
p.LotStep = SnapLotStep(lotStepFromUnits);
if (!(p.LotStep > 0)) p.LotStep = 0.01;
double minLot = (minLotFromUnits > 0) ? minLotFromUnits : p.LotStep;
p.MinLot = Math.Max(p.LotStep, minLot);
}
else
{
p.LotStep = (LotStepOverride > 0) ? LotStepOverride : 0.01;
p.MinLot = (MinLotOverride > 0) ? MinLotOverride : p.LotStep;
}
// Units-Schritt passend zum LotStep, aber nie kleiner als Broker-Step
long stepUnitsFromLots = (long)Math.Round(p.UnitsPerLot * p.LotStep);
if (stepUnitsFromLots <= 0) stepUnitsFromLots = 1;
p.UnitStep = Math.Max(p.UnitStepBroker, stepUnitsFromLots);
// PipValue je Lot (für Logging) aus AmountRisked: 1 Pip SL → Verlust pro Lot
try
{
double volOneLot = s.QuantityToVolumeInUnits(1.0);
p.PipValuePerLot = s.AmountRisked(volOneLot, 1.0);
}
catch
{
p.PipValuePerLot = double.NaN;
p.Warn += "PipValuePerLot calc failed; ";
}
p.Valid = true;
return p;
}
private static double SnapLotStep(double raw)
{
if (!(raw > 0)) return 0.01;
// Snap auf 0.001 / 0.01 / 0.1 / 1
double[] grid = { 0.001, 0.01, 0.1, 1.0 };
double best = grid[0];
double bd = Math.Abs(raw - best);
foreach (var g in grid)
{
double d = Math.Abs(raw - g);
if (d < bd)
{
bd = d;
best = g;
}
}
return best;
}
// ---------- Risiko in Lots (über VolumeForFixedRisk) ----------
private struct RiskCalc
{
public bool Ok;
public string Msg;
public double PipDist;
public double LossPerLot;
public double LotsRaw;
}
private RiskCalc ComputeLots(Symbol s, double entry, double sl, double riskMoney)
{
var r = new RiskCalc { Ok = false, Msg = "unknown" };
// Stop-Abstand in Pips
double pipDist = Math.Abs(entry - sl) / s.PipSize;
if (pipDist <= 0)
{
r.Msg = "pipDist<=0";
return r;
}
// Volume in Units für das gewünschte Geld-Risiko
double volRaw;
try
{
volRaw = s.VolumeForFixedRisk(riskMoney, pipDist);
}
catch
{
r.Msg = "VolumeForFixedRisk failed";
return r;
}
if (!(volRaw > 0))
{
r.Msg = "VolumeForFixedRisk<=0";
return r;
}
// Lots aus Units
double lots = s.VolumeInUnitsToQuantity(volRaw);
// Verlust pro 1 Lot (für Logging / MinRiskNeeded)
double volOneLot = s.QuantityToVolumeInUnits(1.0);
double lossPerLot = s.AmountRisked(volOneLot, pipDist);
if (!(lossPerLot > 0))
{
r.Msg = "lossPerLot<=0";
return r;
}
r.Ok = true;
r.Msg = "ok";
r.PipDist = pipDist;
r.LossPerLot = lossPerLot;
r.LotsRaw = lots;
return r;
}
// ---------- Lots ⇄ Units ----------
private long LotsToUnits(SymbolProfile p, double lots)
{
double rawUnits = lots * p.UnitsPerLot;
long units = (long)(Math.Floor(rawUnits / p.UnitStep) * p.UnitStep);
return units;
}
private double UnitsToLots(SymbolProfile p, long units)
{
return units / p.UnitsPerLot;
}
// ---------- SL / TP ----------
private static double ComputeTP(double entry, double sl, TradeType side, double r)
{
double d = Math.Abs(entry - sl);
return side == TradeType.Buy ? entry + r * d : entry - r * d;
}
private static double RoundDownToTick(Symbol s, double price)
{
var steps = Math.Floor(price / s.TickSize);
return steps * s.TickSize;
}
private static double RoundUpToTick(Symbol s, double price)
{
var steps = Math.Ceiling(price / s.TickSize);
return steps * s.TickSize;
}
private (double nSL, double nTP) NormalizeSLTP(Symbol s, TradeType side, double entry, double rawSL, double rawTP)
{
double nSL, nTP;
if (side == TradeType.Buy)
{
nSL = RoundDownToTick(s, Math.Min(rawSL, entry - s.TickSize));
nTP = RoundUpToTick(s, Math.Max(rawTP, entry + s.TickSize));
}
else
{
nSL = RoundUpToTick(s, Math.Max(rawSL, entry + s.TickSize));
nTP = RoundDownToTick(s, Math.Min(rawTP, entry - s.TickSize));
}
return (nSL, nTP);
}
// ---------- Pending kill ----------
private void CancelPendingOrder(PendingOrder po)
{
try
{
var r = CancelPendingOrderAsync(po);
Log($"[CANCEL] pending #{po.Id} sent");
}
catch (Exception ex)
{
Log($"[WARN] cancel pending #{po.Id}: {ex.Message}");
}
}
// ---------- Logging ----------
private void Log(string msg)
{
if (!DebugLogs && !DebugOverlay) return;
string line = $"{Server.Time:HH:mm:ss} {msg}";
if (DebugLogs) Print(line);
if (DebugOverlay)
{
_ol.Add(line);
if (_ol.Count > 16) _ol.RemoveAt(0);
Chart.DrawStaticText("unirisk_dbg", string.Join("\n", _ol),
VerticalAlignment.Top, HorizontalAlignment.Left, Color.Yellow);
}
}
}
}
```
Han respondido
1
Evaluación
Proyectos
6
17%
Arbitraje
1
0%
/
0%
Caducado
0
Trabaja
1
Evaluación
Proyectos
29
14%
Arbitraje
3
0%
/
67%
Caducado
3
10%
Trabaja
2
Evaluación
Proyectos
470
39%
Arbitraje
102
40%
/
24%
Caducado
78
17%
Ocupado
Ha publicado: 2 ejemplos
3
Evaluación
Proyectos
6
0%
Arbitraje
0
Caducado
0
Trabaja
4
Evaluación
Proyectos
38
24%
Arbitraje
14
0%
/
93%
Caducado
4
11%
Libre
5
Evaluación
Proyectos
0
0%
Arbitraje
1
0%
/
100%
Caducado
0
Libre
Información sobre el proyecto
Presupuesto
43+ USD