Erstellen Sie Ihre Grid-Strategie
Die Build Your Grid Strategy ist eine direkte Umsetzung des MetaTrader Expertenberaters „BuildYourGridEA“. Es hält zwei unabhängig Dent Ladders von Marktpositionen auf der Long- und Short-Seite und fügt neue Ebenen hinzu, wenn der Preis um eine konfigurierbare Anzahl von Pip steigt s und erhöht optional das gehandelte Volumen geometrisch oder exponentiell. Der Warenkorb kann bei Erreichen eines kombinierten Gewinnziels geschlossen werden et wird erreicht, wenn ein in Pips gemessener maximaler Verlust überschritten wird, oder durch die Erteilung von Absicherungsaufträgen bei Unterbrechung des Floating Drawdowns Er ist ein Prozentsatz des Kontostands.
Wie es funktioniert
- Erste Eingaben. Abhängig von der Auftragserteilung eröffnet die Strategie die ersten Kauf-, Verkaufs- oder beide Marktaufträge, sobald die Spread-Bedingungen dies zulassen.
- Netzausbau. Zusätzliche Aufträge werden entweder mit oder gegen den Trend ausgelöst. Der Abstand zur nächsten Ebene wird in Pips gemessen, wahlweise multipliziert mit der Anzahl bereits offener Orders oder einer Zweierpotenz.
- Volumenverlauf. Die Auftragsgröße folgt der ausgewählten Lot-Verlaufsregel (statisch, geometrisch oder exponentiell) und kann durch den Max. Multiplikator relativ zum ersten Eintrag begrenzt werden.
- Gewinnmitnahmen. Der gesamte Korb wird geschlossen, sobald der gesamte variable PnL das Ziel überschreitet, das entweder in Pips oder in der Kontowährung ausgedrückt wird.
- Verlustschutz. Wenn der kumulative Verlust den konfigurierten Pip-Schwellenwert überschreitet, schließt die Strategie je nach Verlustbehandlung-Modus entweder das älteste Ticket auf jeder Seite oder den gesamten Korb.
- Absicherung. Wenn der variable Drawdown den Hedge-Schwellenwert (%) erreicht, wird eine Ausgleichsorder in der Größe der Volumendifferenz und des Hedge-Multiplikators eingereicht, um das Engagement einzufrieren.
Parameter
| Parameter | Beschreibung |
|---|---|
Order Placement |
Welche Richtungen zum Öffnen neuer Ebenen zulässig sind (beide, nur lang, nur kurz). |
Grid Direction |
Ob zusätzliche Aufträge dem Trend folgen oder die Bewegung abschwächen. |
Grid Step (pips) |
Basisabstand in Pips zur nächsten Ebene, bevor Multiplikatoren angewendet werden. |
Step Progression |
Statischer Abstand, geometrisches Wachstum (× count) oder exponentielles Wachstum (× 2^(n-1)). |
Close Target |
Art des Gewinnziels (Pips oder Kontowährung). |
Target (pips) / Target (currency) |
Schwellenwert, der überschritten werden muss, um den Korb mit Gewinn zu schließen. |
Loss Handling |
Aktion, wenn das Pip-Drawdown-Limit erreicht wird (nichts tun, die ersten Tickets schließen oder alle schließen). |
Loss (pips) |
Maximal tolerierter kombinierter Verlust, bevor der Schutz greift. |
Use Hedge |
Ermöglicht Hedge-Orders zum Ausgleich des Nettorisikos bei starken Drawdowns. |
Hedge Threshold (%) |
Prozentsatz des Kontostands, der als Auslöser für die Absicherung verwendet wird. |
Hedge Multiplier |
Bei der Erteilung des Sicherungsauftrags wird ein Multiplikator auf die Volumendifferenz angewendet. |
Auto Volume / Risk Factor |
Balancegesteuerte Positionsgrößenbestimmung. Volumen = Saldo × Risikofaktor / 100000. |
Manual Volume |
Die Losgröße wurde korrigiert, wenn die automatische Größenbestimmung deaktiviert ist. |
Lot Progression |
Statische, geometrische oder exponentielle Skalierung für aufeinanderfolgende Ordnungen. |
Max Multiplier |
Begrenzt die Losgröße auf firstLot × MaxMultiplier. |
Max Orders |
Maximale Anzahl gleichzeitig offener Positionen (0 = unbegrenzt). |
Max Spread |
Blockiert neue Trades, während der Spread in Pips über dem Schwellenwert liegt (0 = ignorieren). |
Use Completed Bar / Candle Type |
Signale nur einmal pro abgeschlossener Kerze des ausgewählten Typs auswerten. |
Nutzungshinweise
- Die Strategie basiert auf den besten Bid/Ask-Aktualisierungen. Konfigurieren Sie Ihren Datenfeed, um Notierungen der Stufe 1 mit genauen Spreads bereitzustellen.
- Absicherungsaufträge hängen vom Portfoliowert ab. Stellen Sie bei der Ausführung im StockSharp-Designer oder -Tester sicher, dass das verbundene Portfolio ein aussagekräftiges Gleichgewicht meldet.
- Grid-Strategien akkumulieren schnell Risiken. Beginnen Sie mit konservativen Volumina und testen Sie die Konfiguration in einer Simulation, bevor Sie sie auf den Live-Handel anwenden.
- Wenn
Use Completed Baraktiviert ist, wird die Handelslogik nur einmal pro fertiger Kerze ausgewertet, was die Option „Abgeschlossene Leiste verwenden“ des ursprünglichen Beraters nachahmt.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Grid strategy converted from the MetaTrader expert "BuildYourGridEA".
/// It maintains layered long and short positions, optionally increases volume geometrically
/// and supports profit/loss group exits together with hedge rebalancing.
/// </summary>
public class BuildYourGridStrategy : Strategy
{
public enum OrderPlacementModes
{
Both,
LongOnly,
ShortOnly,
}
public enum GridDirectionModes
{
WithTrend,
AgainstTrend,
}
public enum StepProgressionModes
{
Static,
Geometric,
Exponential,
}
public enum CloseTargetModes
{
Pips,
Currency,
}
public enum LossCloseModes
{
DoNothing,
CloseFirst,
CloseAll,
}
public enum LotProgressionModes
{
Static,
Geometric,
Exponential,
}
private sealed class PositionEntry
{
public PositionEntry(decimal price, decimal volume)
{
Price = price;
Volume = volume;
}
public decimal Price { get; set; }
public decimal Volume { get; set; }
}
private readonly StrategyParam<OrderPlacementModes> _orderPlacement;
private readonly StrategyParam<GridDirectionModes> _gridDirection;
private readonly StrategyParam<decimal> _pipsForNextOrder;
private readonly StrategyParam<StepProgressionModes> _stepProgression;
private readonly StrategyParam<CloseTargetModes> _closeTargetMode;
private readonly StrategyParam<decimal> _pipsCloseInProfit;
private readonly StrategyParam<decimal> _currencyCloseInProfit;
private readonly StrategyParam<LossCloseModes> _lossCloseMode;
private readonly StrategyParam<decimal> _pipsForCloseInLoss;
private readonly StrategyParam<bool> _placeHedgeOrder;
private readonly StrategyParam<decimal> _hedgeLossThreshold;
private readonly StrategyParam<decimal> _hedgeVolumeMultiplier;
private readonly StrategyParam<bool> _autoLotSize;
private readonly StrategyParam<decimal> _riskFactor;
private readonly StrategyParam<decimal> _manualLotSize;
private readonly StrategyParam<LotProgressionModes> _lotProgression;
private readonly StrategyParam<decimal> _maxMultiplierLot;
private readonly StrategyParam<int> _maxOrders;
private readonly StrategyParam<decimal> _maxSpread;
private readonly StrategyParam<DataType> _candleType;
private readonly List<PositionEntry> _longEntries = new();
private readonly List<PositionEntry> _shortEntries = new();
private decimal _currentPrice;
private decimal _pointSize;
private decimal _priceStep;
private decimal _stepPrice;
private int _totalOrders;
private int _buyOrders;
private int _sellOrders;
private decimal _totalLongVolume;
private decimal _totalShortVolume;
private decimal _buyProfit;
private decimal _sellProfit;
private decimal _buyPips;
private decimal _sellPips;
private decimal? _lastBuyPrice;
private decimal? _lastSellPrice;
private decimal _firstBuyVolume;
private decimal _firstSellVolume;
private decimal _lastBuyVolume;
private decimal _lastSellVolume;
private bool _isHedged;
private int _cooldownBars;
/// <summary>
/// Controls whether the strategy may open both directions or a single side only.
/// </summary>
public OrderPlacementModes OrderPlacement
{
get => _orderPlacement.Value;
set => _orderPlacement.Value = value;
}
/// <summary>
/// Determines if new grid layers follow the trend or fade the movement.
/// </summary>
public GridDirectionModes GridDirection
{
get => _gridDirection.Value;
set => _gridDirection.Value = value;
}
/// <summary>
/// Base distance between consecutive grid layers measured in pips.
/// </summary>
public decimal PipsForNextOrder
{
get => _pipsForNextOrder.Value;
set => _pipsForNextOrder.Value = value;
}
/// <summary>
/// Defines how the grid step evolves with each new order.
/// </summary>
public StepProgressionModes StepProgression
{
get => _stepProgression.Value;
set => _stepProgression.Value = value;
}
/// <summary>
/// Target type used for closing the basket in profit.
/// </summary>
public CloseTargetModes CloseTarget
{
get => _closeTargetMode.Value;
set => _closeTargetMode.Value = value;
}
/// <summary>
/// Profit target expressed in pips.
/// </summary>
public decimal PipsCloseInProfit
{
get => _pipsCloseInProfit.Value;
set => _pipsCloseInProfit.Value = value;
}
/// <summary>
/// Profit target expressed in account currency.
/// </summary>
public decimal CurrencyCloseInProfit
{
get => _currencyCloseInProfit.Value;
set => _currencyCloseInProfit.Value = value;
}
/// <summary>
/// Defines how the basket is closed when the floating loss limit is reached.
/// </summary>
public LossCloseModes LossMode
{
get => _lossCloseMode.Value;
set => _lossCloseMode.Value = value;
}
/// <summary>
/// Maximal allowed loss in pips before defensive actions.
/// </summary>
public decimal PipsForCloseInLoss
{
get => _pipsForCloseInLoss.Value;
set => _pipsForCloseInLoss.Value = value;
}
/// <summary>
/// Enables hedge orders when losses exceed a percentage of the balance.
/// </summary>
public bool PlaceHedgeOrder
{
get => _placeHedgeOrder.Value;
set => _placeHedgeOrder.Value = value;
}
/// <summary>
/// Loss percentage of the balance that triggers hedging.
/// </summary>
public decimal HedgeLossThreshold
{
get => _hedgeLossThreshold.Value;
set => _hedgeLossThreshold.Value = value;
}
/// <summary>
/// Multiplier applied to the imbalance volume when hedging.
/// </summary>
public decimal HedgeVolumeMultiplier
{
get => _hedgeVolumeMultiplier.Value;
set => _hedgeVolumeMultiplier.Value = value;
}
/// <summary>
/// Uses balance based sizing when enabled.
/// </summary>
public bool AutoLotSize
{
get => _autoLotSize.Value;
set => _autoLotSize.Value = value;
}
/// <summary>
/// Risk factor used for automatic volume calculation.
/// </summary>
public decimal RiskFactor
{
get => _riskFactor.Value;
set => _riskFactor.Value = value;
}
/// <summary>
/// Manual order volume when automatic sizing is disabled.
/// </summary>
public decimal ManualLotSize
{
get => _manualLotSize.Value;
set => _manualLotSize.Value = value;
}
/// <summary>
/// Controls how the lot size grows with new orders.
/// </summary>
public LotProgressionModes LotProgression
{
get => _lotProgression.Value;
set => _lotProgression.Value = value;
}
/// <summary>
/// Caps the lot size to a multiple of the first entry.
/// </summary>
public decimal MaxMultiplierLot
{
get => _maxMultiplierLot.Value;
set => _maxMultiplierLot.Value = value;
}
/// <summary>
/// Maximum amount of simultaneous orders (0 means unlimited).
/// </summary>
public int MaxOrders
{
get => _maxOrders.Value;
set => _maxOrders.Value = value;
}
/// <summary>
/// Maximum acceptable spread expressed in pips.
/// </summary>
public decimal MaxSpread
{
get => _maxSpread.Value;
set => _maxSpread.Value = value;
}
/// <summary>
/// Candle type used for price data.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="BuildYourGridStrategy"/> class.
/// </summary>
public BuildYourGridStrategy()
{
_orderPlacement = Param(nameof(OrderPlacement), OrderPlacementModes.LongOnly)
.SetDisplay("Order Placement", "Allowed entry direction", "General");
_gridDirection = Param(nameof(GridDirection), GridDirectionModes.AgainstTrend)
.SetDisplay("Grid Direction", "Whether layers follow or fade the trend", "Grid");
_pipsForNextOrder = Param(nameof(PipsForNextOrder), 500000m)
.SetDisplay("Grid Step (pips)", "Base spacing between grid levels", "Grid")
.SetGreaterThanZero();
_stepProgression = Param(nameof(StepProgression), StepProgressionModes.Static)
.SetDisplay("Step Progression", "How the distance grows with each layer", "Grid");
_closeTargetMode = Param(nameof(CloseTarget), CloseTargetModes.Pips)
.SetDisplay("Close Target", "Profit target type", "Risk");
_pipsCloseInProfit = Param(nameof(PipsCloseInProfit), 500000m)
.SetDisplay("Target (pips)", "Basket profit target in pips", "Risk")
.SetGreaterThanZero();
_currencyCloseInProfit = Param(nameof(CurrencyCloseInProfit), 10m)
.SetDisplay("Target (currency)", "Basket profit target in account currency", "Risk")
.SetGreaterThanZero();
_lossCloseMode = Param(nameof(LossMode), LossCloseModes.DoNothing)
.SetDisplay("Loss Handling", "Action when the loss threshold is hit", "Risk");
_pipsForCloseInLoss = Param(nameof(PipsForCloseInLoss), 200000m)
.SetDisplay("Loss (pips)", "Allowed drawdown before protective close", "Risk")
.SetGreaterThanZero();
_placeHedgeOrder = Param(nameof(PlaceHedgeOrder), false)
.SetDisplay("Use Hedge", "Enable hedge rebalancing", "Risk");
_hedgeLossThreshold = Param(nameof(HedgeLossThreshold), 10m)
.SetDisplay("Hedge Threshold (%)", "Loss percentage that triggers hedging", "Risk")
.SetGreaterThanZero();
_hedgeVolumeMultiplier = Param(nameof(HedgeVolumeMultiplier), 1m)
.SetDisplay("Hedge Multiplier", "Multiplier applied to imbalance volume", "Risk")
.SetGreaterThanZero();
_autoLotSize = Param(nameof(AutoLotSize), false)
.SetDisplay("Auto Volume", "Use balance driven order size", "Volume");
_riskFactor = Param(nameof(RiskFactor), 1m)
.SetDisplay("Risk Factor", "Risk factor for automatic sizing", "Volume")
.SetGreaterThanZero();
_manualLotSize = Param(nameof(ManualLotSize), 0.01m)
.SetDisplay("Manual Volume", "Order size when auto sizing is disabled", "Volume")
.SetGreaterThanZero();
_lotProgression = Param(nameof(LotProgression), LotProgressionModes.Static)
.SetDisplay("Lot Progression", "How volumes scale with each layer", "Volume");
_maxMultiplierLot = Param(nameof(MaxMultiplierLot), 50m)
.SetDisplay("Max Multiplier", "Cap for lot growth relative to the first entry", "Volume")
.SetGreaterThanZero();
_maxOrders = Param(nameof(MaxOrders), 2)
.SetDisplay("Max Orders", "Maximum simultaneous positions (0 = unlimited)", "General")
.SetRange(0, 1000);
_maxSpread = Param(nameof(MaxSpread), 0m)
.SetDisplay("Max Spread", "Maximum allowed spread in pips (0 = ignore)", "General")
.SetNotNegative();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle series used for price data", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_longEntries.Clear();
_shortEntries.Clear();
_currentPrice = 0m;
_pointSize = 0m;
_priceStep = 0m;
_stepPrice = 0m;
_totalOrders = 0;
_buyOrders = 0;
_sellOrders = 0;
_totalLongVolume = 0m;
_totalShortVolume = 0m;
_buyProfit = 0m;
_sellProfit = 0m;
_buyPips = 0m;
_sellPips = 0m;
_lastBuyPrice = null;
_lastSellPrice = null;
_firstBuyVolume = 0m;
_firstSellVolume = 0m;
_lastBuyVolume = 0m;
_lastSellVolume = 0m;
_isHedged = false;
_cooldownBars = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointSize = CalculatePointSize();
_priceStep = Security?.PriceStep ?? 0m;
_stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 0m;
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_currentPrice = candle.ClosePrice;
if (_currentPrice <= 0m)
return;
ProcessPrices();
}
private void ProcessPrices()
{
if (_cooldownBars > 0)
{
_cooldownBars--;
return;
}
UpdateAggregates();
if (_totalOrders > 0)
{
if (ShouldCloseInProfit())
{
if (CloseAllPositions())
{
_cooldownBars = 200;
return;
}
}
if (LossMode != LossCloseModes.DoNothing && ShouldCloseInLoss())
{
var closed = LossMode == LossCloseModes.CloseFirst ? CloseFirstPositions() : CloseAllPositions();
if (closed)
{
_cooldownBars = 200;
return;
}
}
if (ShouldHedge())
{
if (ExecuteHedgeOrder())
return;
}
}
if (TryOpenInitialOrders())
return;
TryOpenNextOrders();
}
private void UpdateAggregates()
{
_totalOrders = _longEntries.Count + _shortEntries.Count;
_buyOrders = _longEntries.Count;
_sellOrders = _shortEntries.Count;
_totalLongVolume = 0m;
_totalShortVolume = 0m;
_buyProfit = 0m;
_sellProfit = 0m;
_buyPips = 0m;
_sellPips = 0m;
_lastBuyPrice = null;
_lastSellPrice = null;
foreach (var entry in _longEntries)
{
_totalLongVolume += entry.Volume;
_lastBuyPrice = entry.Price;
var diff = _currentPrice - entry.Price;
_buyProfit += CalculateProfit(diff, entry.Volume);
_buyPips += CalculatePips(diff);
}
foreach (var entry in _shortEntries)
{
_totalShortVolume += entry.Volume;
_lastSellPrice = entry.Price;
var diff = entry.Price - _currentPrice;
_sellProfit += CalculateProfit(diff, entry.Volume);
_sellPips += CalculatePips(diff);
}
_firstBuyVolume = _longEntries.Count > 0 ? _longEntries[0].Volume : 0m;
_firstSellVolume = _shortEntries.Count > 0 ? _shortEntries[0].Volume : 0m;
_lastBuyVolume = _longEntries.Count > 0 ? _longEntries[^1].Volume : 0m;
_lastSellVolume = _shortEntries.Count > 0 ? _shortEntries[^1].Volume : 0m;
_isHedged = _buyOrders > 1 && _sellOrders > 1 && _totalLongVolume == _totalShortVolume && _totalLongVolume > 0m;
}
private decimal CalculateProfit(decimal diff, decimal volume)
{
if (_priceStep > 0m && _stepPrice > 0m)
return diff / _priceStep * _stepPrice * volume;
return diff * volume;
}
private decimal CalculatePips(decimal diff)
{
if (_pointSize > 0m)
return diff / _pointSize;
return diff;
}
private bool ShouldCloseInProfit()
{
var entries = _buyOrders + _sellOrders;
if (entries <= 0)
return false;
return CloseTarget switch
{
CloseTargetModes.Pips => (_buyPips + _sellPips) / entries >= PipsCloseInProfit,
CloseTargetModes.Currency => (_buyProfit + _sellProfit) >= CurrencyCloseInProfit,
_ => false,
};
}
private bool ShouldCloseInLoss()
{
var entries = _buyOrders + _sellOrders;
if (entries <= 0)
return false;
return (_buyPips + _sellPips) / entries <= -PipsForCloseInLoss;
}
private bool CloseAllPositions()
{
var closed = false;
if (_buyOrders > 0)
{
var volume = _totalLongVolume;
_longEntries.Clear();
if (volume > 0m)
{
SellMarket(volume);
closed = true;
}
}
if (_sellOrders > 0)
{
var volume = _totalShortVolume;
_shortEntries.Clear();
if (volume > 0m)
{
BuyMarket(volume);
closed = true;
}
}
return closed;
}
private bool CloseFirstPositions()
{
var closed = false;
if (_buyOrders > 0)
{
var volume = _longEntries[0].Volume;
_longEntries.RemoveAt(0);
if (volume > 0m)
{
SellMarket(volume);
closed = true;
}
}
if (_sellOrders > 0)
{
var volume = _shortEntries[0].Volume;
_shortEntries.RemoveAt(0);
if (volume > 0m)
{
BuyMarket(volume);
closed = true;
}
}
return closed;
}
private bool ShouldHedge()
{
if (!PlaceHedgeOrder || HedgeLossThreshold <= 0m)
return false;
var balance = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
if (balance <= 0m)
return false;
var floating = _buyProfit + _sellProfit;
if (floating >= 0m)
return false;
var lossPercent = Math.Abs(floating) * 100m / balance;
return lossPercent >= HedgeLossThreshold && !_isHedged;
}
private bool ExecuteHedgeOrder()
{
var imbalance = _totalLongVolume - _totalShortVolume;
if (imbalance == 0m)
return false;
if (imbalance < 0m)
{
var volume = NormalizeVolume(Math.Abs(imbalance) * HedgeVolumeMultiplier);
if (volume <= 0m)
return false;
_longEntries.Add(new PositionEntry(_currentPrice, volume));
BuyMarket(volume);
return true;
}
var sellVolume = NormalizeVolume(imbalance * HedgeVolumeMultiplier);
if (sellVolume <= 0m)
return false;
_shortEntries.Add(new PositionEntry(_currentPrice, sellVolume));
SellMarket(sellVolume);
return true;
}
private bool TryOpenInitialOrders()
{
if (!CanOpenMoreOrders())
return false;
if (_buyOrders == 0 && (OrderPlacement == OrderPlacementModes.Both || OrderPlacement == OrderPlacementModes.LongOnly))
{
var volume = GetOrderVolume(Sides.Buy);
if (SendMarketOrder(Sides.Buy, volume))
return true;
}
if (_sellOrders == 0 && (OrderPlacement == OrderPlacementModes.Both || OrderPlacement == OrderPlacementModes.ShortOnly))
{
var volume = GetOrderVolume(Sides.Sell);
if (SendMarketOrder(Sides.Sell, volume))
return true;
}
return false;
}
private void TryOpenNextOrders()
{
if (!CanOpenMoreOrders())
return;
var allowBuy = OrderPlacement != OrderPlacementModes.ShortOnly;
var allowSell = OrderPlacement != OrderPlacementModes.LongOnly;
if (!allowBuy && !allowSell)
return;
if ((_buyOrders > 0 || OrderPlacement == OrderPlacementModes.ShortOnly)
&& (_sellOrders > 0 || OrderPlacement == OrderPlacementModes.LongOnly))
{
var buyDistance = allowBuy ? GetNextDistance(Sides.Buy) : 0m;
var sellDistance = allowSell ? GetNextDistance(Sides.Sell) : 0m;
if (GridDirection == GridDirectionModes.WithTrend)
{
if (allowBuy && _lastBuyPrice.HasValue && buyDistance > 0m)
{
var trigger = _lastBuyPrice.Value + buyDistance;
if (_currentPrice >= trigger)
{
var volume = GetOrderVolume(Sides.Buy);
if (SendMarketOrder(Sides.Buy, volume))
return;
}
}
if (allowSell && _lastSellPrice.HasValue && sellDistance > 0m)
{
var trigger = _lastSellPrice.Value - sellDistance;
if (_currentPrice <= trigger)
{
var volume = GetOrderVolume(Sides.Sell);
if (SendMarketOrder(Sides.Sell, volume))
return;
}
}
}
else
{
if (allowBuy && _lastBuyPrice.HasValue && buyDistance > 0m)
{
var trigger = _lastBuyPrice.Value - buyDistance;
if (_currentPrice <= trigger)
{
var volume = GetOrderVolume(Sides.Buy);
if (SendMarketOrder(Sides.Buy, volume))
return;
}
}
if (allowSell && _lastSellPrice.HasValue && sellDistance > 0m)
{
var trigger = _lastSellPrice.Value + sellDistance;
if (_currentPrice >= trigger)
{
var volume = GetOrderVolume(Sides.Sell);
if (SendMarketOrder(Sides.Sell, volume))
return;
}
}
}
}
}
private bool CanOpenMoreOrders()
{
if (MaxOrders <= 0)
return true;
return _totalOrders < MaxOrders;
}
private decimal GetNextDistance(Sides side)
{
var baseDistance = PipsForNextOrder;
var count = side == Sides.Buy ? _buyOrders : _sellOrders;
var multiplier = StepProgression switch
{
StepProgressionModes.Static => 1m,
StepProgressionModes.Geometric => Math.Max(1, count),
StepProgressionModes.Exponential => count <= 0 ? 1m : (decimal)Math.Max(1, Math.Pow(2, count - 1)),
_ => 1m,
};
return baseDistance * multiplier * _pointSize;
}
private decimal GetOrderVolume(Sides side)
{
var baseVolume = GetBaseVolume();
var firstVolume = side == Sides.Buy ? _firstBuyVolume : _firstSellVolume;
var lastVolume = side == Sides.Buy ? _lastBuyVolume : _lastSellVolume;
var orders = side == Sides.Buy ? _buyOrders : _sellOrders;
decimal result;
switch (LotProgression)
{
case LotProgressionModes.Static:
result = orders == 0 ? baseVolume : (firstVolume > 0m ? firstVolume : baseVolume);
break;
case LotProgressionModes.Geometric:
if (orders == 0)
{
result = baseVolume;
}
else if (orders == 1)
{
result = lastVolume * 2m;
}
else
{
result = lastVolume + (firstVolume > 0m ? firstVolume : baseVolume);
}
break;
case LotProgressionModes.Exponential:
result = orders == 0 ? baseVolume : lastVolume * 2m;
break;
default:
result = baseVolume;
break;
}
if (MaxMultiplierLot > 0m && orders > 0 && firstVolume > 0m)
{
var cap = firstVolume * MaxMultiplierLot;
if (result > cap)
result = cap;
}
return NormalizeVolume(result);
}
private decimal GetBaseVolume()
{
decimal volume;
if (AutoLotSize)
{
var balance = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
volume = balance > 0m ? balance * RiskFactor / 100000m : ManualLotSize;
}
else
{
volume = ManualLotSize;
}
return NormalizeVolume(volume);
}
private bool SendMarketOrder(Sides side, decimal volume)
{
if (volume <= 0m)
return false;
if (side == Sides.Buy)
{
_longEntries.Add(new PositionEntry(_currentPrice, volume));
BuyMarket(volume);
}
else
{
_shortEntries.Add(new PositionEntry(_currentPrice, volume));
SellMarket(volume);
}
return true;
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var min = Security?.MinVolume ?? 0m;
var max = Security?.MaxVolume ?? 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m)
volume = Math.Round(volume / step, 0, MidpointRounding.AwayFromZero) * step;
if (min > 0m)
volume = Math.Max(volume, min);
if (max > 0m)
volume = Math.Min(volume, max);
return volume;
}
private decimal CalculatePointSize()
{
var step = Security?.PriceStep ?? 0m;
return step > 0m ? step : 0.0001m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Level1Fields, Sides
from StockSharp.Algo.Strategies import Strategy
class build_your_grid_strategy(Strategy):
"""Grid strategy converted from the MetaTrader expert 'BuildYourGridEA'.
It maintains layered long and short positions, optionally increases volume geometrically
and supports profit/loss group exits together with hedge rebalancing."""
# OrderPlacementModes
OP_BOTH = 0
OP_LONG_ONLY = 1
OP_SHORT_ONLY = 2
# GridDirectionModes
GD_WITH_TREND = 0
GD_AGAINST_TREND = 1
# StepProgressionModes
SP_STATIC = 0
SP_GEOMETRIC = 1
SP_EXPONENTIAL = 2
# CloseTargetModes
CT_PIPS = 0
CT_CURRENCY = 1
# LossCloseModes
LC_DO_NOTHING = 0
LC_CLOSE_FIRST = 1
LC_CLOSE_ALL = 2
# LotProgressionModes
LP_STATIC = 0
LP_GEOMETRIC = 1
LP_EXPONENTIAL = 2
def __init__(self):
super(build_your_grid_strategy, self).__init__()
self._order_placement = self.Param("OrderPlacement", self.OP_LONG_ONLY) \
.SetDisplay("Order Placement", "Allowed entry direction", "General")
self._grid_direction = self.Param("GridDirection", self.GD_AGAINST_TREND) \
.SetDisplay("Grid Direction", "Whether layers follow or fade the trend", "Grid")
self._pips_for_next_order = self.Param("PipsForNextOrder", 500000.0) \
.SetGreaterThanZero() \
.SetDisplay("Grid Step (pips)", "Base spacing between grid levels", "Grid")
self._step_progression = self.Param("StepProgression", self.SP_STATIC) \
.SetDisplay("Step Progression", "How the distance grows with each layer", "Grid")
self._close_target_mode = self.Param("CloseTarget", self.CT_PIPS) \
.SetDisplay("Close Target", "Profit target type", "Risk")
self._pips_close_in_profit = self.Param("PipsCloseInProfit", 500000.0) \
.SetGreaterThanZero() \
.SetDisplay("Target (pips)", "Basket profit target in pips", "Risk")
self._currency_close_in_profit = self.Param("CurrencyCloseInProfit", 10.0) \
.SetGreaterThanZero() \
.SetDisplay("Target (currency)", "Basket profit target in account currency", "Risk")
self._loss_close_mode = self.Param("LossMode", self.LC_DO_NOTHING) \
.SetDisplay("Loss Handling", "Action when loss threshold is hit", "Risk")
self._pips_for_close_in_loss = self.Param("PipsForCloseInLoss", 200000.0) \
.SetGreaterThanZero() \
.SetDisplay("Loss (pips)", "Allowed drawdown before protective close", "Risk")
self._place_hedge_order = self.Param("PlaceHedgeOrder", False) \
.SetDisplay("Use Hedge", "Enable hedge rebalancing", "Risk")
self._hedge_loss_threshold = self.Param("HedgeLossThreshold", 10.0) \
.SetGreaterThanZero() \
.SetDisplay("Hedge Threshold (%)", "Loss percentage that triggers hedging", "Risk")
self._hedge_volume_multiplier = self.Param("HedgeVolumeMultiplier", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Hedge Multiplier", "Multiplier applied to imbalance volume", "Risk")
self._auto_lot_size = self.Param("AutoLotSize", False) \
.SetDisplay("Auto Volume", "Use balance driven order size", "Volume")
self._risk_factor = self.Param("RiskFactor", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Risk Factor", "Risk factor for automatic sizing", "Volume")
self._manual_lot_size = self.Param("ManualLotSize", 0.01) \
.SetGreaterThanZero() \
.SetDisplay("Manual Volume", "Order size when auto sizing is disabled", "Volume")
self._lot_progression = self.Param("LotProgression", self.LP_STATIC) \
.SetDisplay("Lot Progression", "How volumes scale with each layer", "Volume")
self._max_multiplier_lot = self.Param("MaxMultiplierLot", 50.0) \
.SetGreaterThanZero() \
.SetDisplay("Max Multiplier", "Cap for lot growth relative to first entry", "Volume")
self._max_orders = self.Param("MaxOrders", 2) \
.SetDisplay("Max Orders", "Maximum simultaneous positions (0 = unlimited)", "General")
self._max_spread = self.Param("MaxSpread", 0.0) \
.SetDisplay("Max Spread", "Maximum allowed spread in pips (0 = ignore)", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle series used for price data", "General")
self._long_entries = [] # list of [price, volume]
self._short_entries = [] # list of [price, volume]
self._current_price = 0.0
self._point_size = 0.0
self._price_step = 0.0
self._step_price = 0.0
self._total_orders = 0
self._buy_orders = 0
self._sell_orders = 0
self._total_long_volume = 0.0
self._total_short_volume = 0.0
self._buy_profit = 0.0
self._sell_profit = 0.0
self._buy_pips = 0.0
self._sell_pips = 0.0
self._last_buy_price = None
self._last_sell_price = None
self._first_buy_volume = 0.0
self._first_sell_volume = 0.0
self._last_buy_volume = 0.0
self._last_sell_volume = 0.0
self._is_hedged = False
self._cooldown_bars = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def OrderPlacement(self):
return self._order_placement.Value
@property
def GridDirection(self):
return self._grid_direction.Value
@property
def PipsForNextOrder(self):
return self._pips_for_next_order.Value
@property
def StepProgression(self):
return self._step_progression.Value
@property
def CloseTarget(self):
return self._close_target_mode.Value
@property
def PipsCloseInProfit(self):
return self._pips_close_in_profit.Value
@property
def CurrencyCloseInProfit(self):
return self._currency_close_in_profit.Value
@property
def LossMode(self):
return self._loss_close_mode.Value
@property
def PipsForCloseInLoss(self):
return self._pips_for_close_in_loss.Value
@property
def PlaceHedgeOrder(self):
return self._place_hedge_order.Value
@property
def HedgeLossThreshold(self):
return self._hedge_loss_threshold.Value
@property
def HedgeVolumeMultiplier(self):
return self._hedge_volume_multiplier.Value
@property
def AutoLotSize(self):
return self._auto_lot_size.Value
@property
def RiskFactor(self):
return self._risk_factor.Value
@property
def ManualLotSize(self):
return self._manual_lot_size.Value
@property
def LotProgression(self):
return self._lot_progression.Value
@property
def MaxMultiplierLot(self):
return self._max_multiplier_lot.Value
@property
def MaxOrders(self):
return self._max_orders.Value
@property
def MaxSpread(self):
return self._max_spread.Value
def OnReseted(self):
super(build_your_grid_strategy, self).OnReseted()
self._long_entries = []
self._short_entries = []
self._current_price = 0.0
self._point_size = 0.0
self._price_step = 0.0
self._step_price = 0.0
self._total_orders = 0
self._buy_orders = 0
self._sell_orders = 0
self._total_long_volume = 0.0
self._total_short_volume = 0.0
self._buy_profit = 0.0
self._sell_profit = 0.0
self._buy_pips = 0.0
self._sell_pips = 0.0
self._last_buy_price = None
self._last_sell_price = None
self._first_buy_volume = 0.0
self._first_sell_volume = 0.0
self._last_buy_volume = 0.0
self._last_sell_volume = 0.0
self._is_hedged = False
self._cooldown_bars = 0
def OnStarted2(self, time):
super(build_your_grid_strategy, self).OnStarted2(time)
self._point_size = self._calculate_point_size()
ps = self.Security.PriceStep if self.Security is not None else None
self._price_step = float(ps) if ps is not None else 0.0
sp = self.GetSecurityValue[object](Level1Fields.StepPrice)
self._step_price = float(sp) if sp is not None else 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._current_price = float(candle.ClosePrice)
if self._current_price <= 0.0:
return
self._process_prices()
def _process_prices(self):
if self._cooldown_bars > 0:
self._cooldown_bars -= 1
return
self._update_aggregates()
if self._total_orders > 0:
if self._should_close_in_profit():
if self._close_all_positions():
self._cooldown_bars = 200
return
if self.LossMode != self.LC_DO_NOTHING and self._should_close_in_loss():
if self.LossMode == self.LC_CLOSE_FIRST:
closed = self._close_first_positions()
else:
closed = self._close_all_positions()
if closed:
self._cooldown_bars = 200
return
if self._should_hedge():
if self._execute_hedge_order():
return
if self._try_open_initial_orders():
return
self._try_open_next_orders()
def _update_aggregates(self):
self._total_orders = len(self._long_entries) + len(self._short_entries)
self._buy_orders = len(self._long_entries)
self._sell_orders = len(self._short_entries)
self._total_long_volume = 0.0
self._total_short_volume = 0.0
self._buy_profit = 0.0
self._sell_profit = 0.0
self._buy_pips = 0.0
self._sell_pips = 0.0
self._last_buy_price = None
self._last_sell_price = None
for entry in self._long_entries:
self._total_long_volume += entry[1]
self._last_buy_price = entry[0]
diff = self._current_price - entry[0]
self._buy_profit += self._calculate_profit(diff, entry[1])
self._buy_pips += self._calculate_pips(diff)
for entry in self._short_entries:
self._total_short_volume += entry[1]
self._last_sell_price = entry[0]
diff = entry[0] - self._current_price
self._sell_profit += self._calculate_profit(diff, entry[1])
self._sell_pips += self._calculate_pips(diff)
self._first_buy_volume = self._long_entries[0][1] if len(self._long_entries) > 0 else 0.0
self._first_sell_volume = self._short_entries[0][1] if len(self._short_entries) > 0 else 0.0
self._last_buy_volume = self._long_entries[-1][1] if len(self._long_entries) > 0 else 0.0
self._last_sell_volume = self._short_entries[-1][1] if len(self._short_entries) > 0 else 0.0
self._is_hedged = (self._buy_orders > 1 and self._sell_orders > 1
and self._total_long_volume == self._total_short_volume
and self._total_long_volume > 0.0)
def _calculate_profit(self, diff, volume):
if self._price_step > 0.0 and self._step_price > 0.0:
return diff / self._price_step * self._step_price * volume
return diff * volume
def _calculate_pips(self, diff):
if self._point_size > 0.0:
return diff / self._point_size
return diff
def _should_close_in_profit(self):
entries = self._buy_orders + self._sell_orders
if entries <= 0:
return False
if self.CloseTarget == self.CT_PIPS:
return (self._buy_pips + self._sell_pips) / float(entries) >= float(self.PipsCloseInProfit)
elif self.CloseTarget == self.CT_CURRENCY:
return (self._buy_profit + self._sell_profit) >= float(self.CurrencyCloseInProfit)
return False
def _should_close_in_loss(self):
entries = self._buy_orders + self._sell_orders
if entries <= 0:
return False
return (self._buy_pips + self._sell_pips) / float(entries) <= -float(self.PipsForCloseInLoss)
def _close_all_positions(self):
closed = False
if self._buy_orders > 0:
volume = self._total_long_volume
self._long_entries = []
if volume > 0.0:
self.SellMarket(volume)
closed = True
if self._sell_orders > 0:
volume = self._total_short_volume
self._short_entries = []
if volume > 0.0:
self.BuyMarket(volume)
closed = True
return closed
def _close_first_positions(self):
closed = False
if self._buy_orders > 0:
volume = self._long_entries[0][1]
self._long_entries.pop(0)
if volume > 0.0:
self.SellMarket(volume)
closed = True
if self._sell_orders > 0:
volume = self._short_entries[0][1]
self._short_entries.pop(0)
if volume > 0.0:
self.BuyMarket(volume)
closed = True
return closed
def _should_hedge(self):
if not self.PlaceHedgeOrder or float(self.HedgeLossThreshold) <= 0.0:
return False
portfolio = self.Portfolio
balance = 0.0
if portfolio is not None:
cv = portfolio.CurrentValue
bv = portfolio.BeginValue
if cv is not None:
balance = float(cv)
elif bv is not None:
balance = float(bv)
if balance <= 0.0:
return False
floating = self._buy_profit + self._sell_profit
if floating >= 0.0:
return False
loss_percent = abs(floating) * 100.0 / balance
return loss_percent >= float(self.HedgeLossThreshold) and not self._is_hedged
def _execute_hedge_order(self):
imbalance = self._total_long_volume - self._total_short_volume
if imbalance == 0.0:
return False
if imbalance < 0.0:
volume = self._normalize_volume(abs(imbalance) * float(self.HedgeVolumeMultiplier))
if volume <= 0.0:
return False
self._long_entries.append([self._current_price, volume])
self.BuyMarket(volume)
return True
sell_volume = self._normalize_volume(imbalance * float(self.HedgeVolumeMultiplier))
if sell_volume <= 0.0:
return False
self._short_entries.append([self._current_price, sell_volume])
self.SellMarket(sell_volume)
return True
def _can_open_more_orders(self):
if self.MaxOrders <= 0:
return True
return self._total_orders < self.MaxOrders
def _try_open_initial_orders(self):
if not self._can_open_more_orders():
return False
if self._buy_orders == 0 and (self.OrderPlacement == self.OP_BOTH or self.OrderPlacement == self.OP_LONG_ONLY):
volume = self._get_order_volume(Sides.Buy)
if self._send_market_order(Sides.Buy, volume):
return True
if self._sell_orders == 0 and (self.OrderPlacement == self.OP_BOTH or self.OrderPlacement == self.OP_SHORT_ONLY):
volume = self._get_order_volume(Sides.Sell)
if self._send_market_order(Sides.Sell, volume):
return True
return False
def _try_open_next_orders(self):
if not self._can_open_more_orders():
return
allow_buy = self.OrderPlacement != self.OP_SHORT_ONLY
allow_sell = self.OrderPlacement != self.OP_LONG_ONLY
if not allow_buy and not allow_sell:
return
has_buys = self._buy_orders > 0 or self.OrderPlacement == self.OP_SHORT_ONLY
has_sells = self._sell_orders > 0 or self.OrderPlacement == self.OP_LONG_ONLY
if not (has_buys and has_sells):
return
buy_distance = self._get_next_distance(Sides.Buy) if allow_buy else 0.0
sell_distance = self._get_next_distance(Sides.Sell) if allow_sell else 0.0
if self.GridDirection == self.GD_WITH_TREND:
if allow_buy and self._last_buy_price is not None and buy_distance > 0.0:
trigger = self._last_buy_price + buy_distance
if self._current_price >= trigger:
volume = self._get_order_volume(Sides.Buy)
if self._send_market_order(Sides.Buy, volume):
return
if allow_sell and self._last_sell_price is not None and sell_distance > 0.0:
trigger = self._last_sell_price - sell_distance
if self._current_price <= trigger:
volume = self._get_order_volume(Sides.Sell)
if self._send_market_order(Sides.Sell, volume):
return
else:
if allow_buy and self._last_buy_price is not None and buy_distance > 0.0:
trigger = self._last_buy_price - buy_distance
if self._current_price <= trigger:
volume = self._get_order_volume(Sides.Buy)
if self._send_market_order(Sides.Buy, volume):
return
if allow_sell and self._last_sell_price is not None and sell_distance > 0.0:
trigger = self._last_sell_price + sell_distance
if self._current_price >= trigger:
volume = self._get_order_volume(Sides.Sell)
if self._send_market_order(Sides.Sell, volume):
return
def _send_market_order(self, side, volume):
if volume <= 0.0:
return False
if side == Sides.Buy:
self._long_entries.append([self._current_price, volume])
self.BuyMarket(volume)
else:
self._short_entries.append([self._current_price, volume])
self.SellMarket(volume)
return True
def _get_next_distance(self, side):
base_distance = float(self.PipsForNextOrder)
count = self._buy_orders if side == Sides.Buy else self._sell_orders
prog = self.StepProgression
if prog == self.SP_STATIC:
multiplier = 1.0
elif prog == self.SP_GEOMETRIC:
multiplier = float(max(1, count))
elif prog == self.SP_EXPONENTIAL:
if count <= 0:
multiplier = 1.0
else:
multiplier = float(max(1, int(Math.Pow(2, count - 1))))
else:
multiplier = 1.0
return base_distance * multiplier * self._point_size
def _get_order_volume(self, side):
base_volume = self._get_base_volume()
is_buy = side == Sides.Buy
first_volume = self._first_buy_volume if is_buy else self._first_sell_volume
last_volume = self._last_buy_volume if is_buy else self._last_sell_volume
orders = self._buy_orders if is_buy else self._sell_orders
prog = self.LotProgression
if prog == self.LP_STATIC:
if orders == 0:
result = base_volume
else:
result = first_volume if first_volume > 0.0 else base_volume
elif prog == self.LP_GEOMETRIC:
if orders == 0:
result = base_volume
elif orders == 1:
result = last_volume * 2.0
else:
result = last_volume + (first_volume if first_volume > 0.0 else base_volume)
elif prog == self.LP_EXPONENTIAL:
if orders == 0:
result = base_volume
else:
result = last_volume * 2.0
else:
result = base_volume
max_mult = float(self.MaxMultiplierLot)
if max_mult > 0.0 and orders > 0 and first_volume > 0.0:
cap = first_volume * max_mult
if result > cap:
result = cap
return self._normalize_volume(result)
def _get_base_volume(self):
if self.AutoLotSize:
portfolio = self.Portfolio
balance = 0.0
if portfolio is not None:
cv = portfolio.CurrentValue
bv = portfolio.BeginValue
if cv is not None:
balance = float(cv)
elif bv is not None:
balance = float(bv)
if balance > 0.0:
volume = balance * float(self.RiskFactor) / 100000.0
else:
volume = float(self.ManualLotSize)
else:
volume = float(self.ManualLotSize)
return self._normalize_volume(volume)
def _normalize_volume(self, volume):
if volume <= 0.0:
return 0.0
sec = self.Security
min_vol = float(sec.MinVolume) if sec is not None and sec.MinVolume is not None else 0.0
max_vol = float(sec.MaxVolume) if sec is not None and sec.MaxVolume is not None else 0.0
step = float(sec.VolumeStep) if sec is not None and sec.VolumeStep is not None else 0.0
if step > 0.0:
volume = round(volume / step) * step
if min_vol > 0.0 and volume < min_vol:
volume = min_vol
if max_vol > 0.0 and volume > max_vol:
volume = max_vol
return volume
def _calculate_point_size(self):
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step > 0.0:
return step
return 0.0001
def CreateClone(self):
return build_your_grid_strategy()