Cuadrícula EA Estrategia profesional
Descripción general
La Estrategia profesional Grid EA reproduce el comportamiento principal del asesor experto MetaTrader 4 original. La estrategia combina escalamiento basado en cuadrícula con RSI o entradas de ruptura cronometradas y funciones de gestión de riesgos virtuales, como punto de equilibrio y paradas finales. Está diseñado para carteras netas, lo que significa que siempre funciona con una única posición neta y borra automáticamente la dirección opuesta cuando se abre una nueva operación.
Lógica de trading
- Modo de entrada: elija entre umbrales RSI, desgloses controlados por tiempo o operación totalmente manual. En modo manual, la estrategia solo gestiona las posiciones existentes y el escalado de la cuadrícula.
- Filtro direccional: restringe el comercio a direcciones largas, cortas o en ambas direcciones.
- Escalado de cuadrícula: después de la entrada inicial, la estrategia puede agregar posiciones cuando el precio retrocede en un número configurable de puntos. Tanto el paso como el volumen del pedido pueden crecer geométricamente.
- Controles de riesgo: los filtros virtuales de stop-loss, take-profit, breakeven, trailing stop y sesión reflejan el comportamiento original del asesor experto.
- Salidas de superposición: los parámetros se proporcionan para completar, pero debido al modelo de posición neta, ambas direcciones no se pueden mantener simultáneamente. Por lo tanto, la lógica de superposición permanece inactiva y los niveles se documentan para compatibilidad futura.
Parámetros
| Nombre | Descripción |
|---|---|
Mode |
Dirección comercial permitida (Compra, Venta, Ambos). |
EntryMode |
Fuente de señal (RSI, Puntos Fijos, Manual). |
RsiPeriod, RsiUpper, RsiLower |
Configuración RSI utilizada en modo RSI. |
CandleType |
Suscripción de velas para señales y gestión de riesgos. |
Distance, TimerSeconds |
Distancia de ruptura e intervalo de actualización para entradas de punto fijo. |
InitialVolume, FromBalance, Risk % |
Bloque de gestión de dinero. Si Risk % > 0, el tamaño de la posición se deriva del capital de la cuenta y la distancia del límite de pérdidas; de lo contrario, se utiliza un lote fijo o basado en el saldo. |
LotMultiplier, MaxLot |
Multiplicador y límite para adiciones a la cuadrícula. |
Step, StepMultiplier, MaxStep |
Configuración de espaciado de cuadrícula en puntos. |
OverlapOrders, OverlapPips |
Reservado para lógica de superposición cubierta (deshabilitada en esta implementación). |
Stop Loss, Take Profit |
Niveles de protección iniciales en puntos (-1 desactiva). |
Break Even Stop, Break Even Step |
Mueva el stop al punto de equilibrio después de que el precio se mueva en el paso definido. |
Trailing Stop, Trailing Step |
Configuración de trailing stop. |
Start Time, End Time |
Ventana de sesión de negociación en hora de la plataforma local (HH:mm). |
Trazar
Cuando el área del gráfico está disponible, la estrategia traza las velas de precios, la línea RSI y todas las operaciones propias, coincidiendo con el diseño del asesor experto fuente.
Notas
- La estrategia cancela automáticamente los niveles de ruptura pendientes una vez que se llenan o cuando la dirección está desactivada.
- Debido a que StockSharp utiliza posiciones netas, solo se puede abrir un lado del mercado a la vez. Abrir una posición larga borra los cortos existentes y viceversa.
- Asegúrese de que las propiedades del instrumento (
PriceStep,StepPrice) estén configuradas de modo que los parámetros basados en puntos coincidan con la configuración original de MT4.
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;
using System.Globalization;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Grid trading strategy that reproduces the behaviour of the Grid EA Pro expert advisor.
/// Combines RSI or timed breakout entries with martingale-style position scaling and virtual risk control.
/// </summary>
public class GridEaProStrategy : Strategy
{
public enum GridTradeModes
{
Buy,
Sell,
Both,
}
public enum GridEntryModes
{
Rsi,
FixedPoints,
Manual,
}
private readonly StrategyParam<GridTradeModes> _mode;
private readonly StrategyParam<GridEntryModes> _entryMode;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiUpperLevel;
private readonly StrategyParam<decimal> _rsiLowerLevel;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _distance;
private readonly StrategyParam<int> _timerSeconds;
private readonly StrategyParam<decimal> _initialVolume;
private readonly StrategyParam<decimal> _fromBalance;
private readonly StrategyParam<decimal> _riskPerTrade;
private readonly StrategyParam<decimal> _lotMultiplier;
private readonly StrategyParam<decimal> _maxLot;
private readonly StrategyParam<int> _stepOrders;
private readonly StrategyParam<decimal> _stepMultiplier;
private readonly StrategyParam<int> _maxStep;
private readonly StrategyParam<int> _overlapOrders;
private readonly StrategyParam<int> _overlapPips;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _breakEvenStop;
private readonly StrategyParam<int> _breakEvenStep;
private readonly StrategyParam<int> _trailingStop;
private readonly StrategyParam<int> _trailingStep;
private readonly StrategyParam<string> _startTime;
private readonly StrategyParam<string> _endTime;
private readonly List<decimal> _longVolumes = new();
private readonly List<decimal> _shortVolumes = new();
private RelativeStrengthIndex _rsi = null!;
private decimal _tickSize;
private decimal _stepValue;
private decimal _tickValue;
private decimal _lastLongPrice;
private decimal _lastShortPrice;
private decimal _lastLongVolume;
private decimal _lastShortVolume;
private decimal? _longStop;
private decimal? _shortStop;
private decimal? _longTake;
private decimal? _shortTake;
private bool _longBreakEven;
private bool _shortBreakEven;
private decimal _longTrailAnchor;
private decimal _shortTrailAnchor;
private decimal? _longNextLevel;
private decimal? _shortNextLevel;
private decimal? _pendingLong;
private decimal? _pendingShort;
private DateTimeOffset? _nextTimer;
/// <summary>
/// Trade direction filter.
/// </summary>
public GridTradeModes Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>
/// Entry mode selection.
/// </summary>
public GridEntryModes EntryMode
{
get => _entryMode.Value;
set => _entryMode.Value = value;
}
/// <summary>
/// RSI lookback period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiUpperLevel
{
get => _rsiUpperLevel.Value;
set => _rsiUpperLevel.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiLowerLevel
{
get => _rsiLowerLevel.Value;
set => _rsiLowerLevel.Value = value;
}
/// <summary>
/// Candle type for signal evaluation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Distance for timed breakout entry (in points).
/// </summary>
public int Distance
{
get => _distance.Value;
set => _distance.Value = value;
}
/// <summary>
/// Interval for recalculating breakout levels.
/// </summary>
public int TimerSeconds
{
get => _timerSeconds.Value;
set => _timerSeconds.Value = value;
}
/// <summary>
/// Base order volume.
/// </summary>
public decimal InitialVolume
{
get => _initialVolume.Value;
set => _initialVolume.Value = value;
}
/// <summary>
/// Reference balance for fixed-lot calculation.
/// </summary>
public decimal FromBalance
{
get => _fromBalance.Value;
set => _fromBalance.Value = value;
}
/// <summary>
/// Risk per trade percentage.
/// </summary>
public decimal RiskPerTrade
{
get => _riskPerTrade.Value;
set => _riskPerTrade.Value = value;
}
/// <summary>
/// Multiplier applied to each additional grid order.
/// </summary>
public decimal LotMultiplier
{
get => _lotMultiplier.Value;
set => _lotMultiplier.Value = value;
}
/// <summary>
/// Maximum allowed order volume.
/// </summary>
public decimal MaxLot
{
get => _maxLot.Value;
set => _maxLot.Value = value;
}
/// <summary>
/// Grid step expressed in points.
/// </summary>
public int StepOrders
{
get => _stepOrders.Value;
set => _stepOrders.Value = value;
}
/// <summary>
/// Multiplier used to expand the grid step after each fill.
/// </summary>
public decimal StepMultiplier
{
get => _stepMultiplier.Value;
set => _stepMultiplier.Value = value;
}
/// <summary>
/// Cap for the adaptive grid step.
/// </summary>
public int MaxStep
{
get => _maxStep.Value;
set => _maxStep.Value = value;
}
/// <summary>
/// Number of opposite orders required to activate overlap logic.
/// </summary>
public int OverlapOrders
{
get => _overlapOrders.Value;
set => _overlapOrders.Value = value;
}
/// <summary>
/// Overlap exit offset in points.
/// </summary>
public int OverlapPips
{
get => _overlapPips.Value;
set => _overlapPips.Value = value;
}
/// <summary>
/// Initial stop-loss distance in points.
/// </summary>
public int StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Initial take-profit distance in points.
/// </summary>
public int TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Breakeven stop offset in points.
/// </summary>
public int BreakEvenStop
{
get => _breakEvenStop.Value;
set => _breakEvenStop.Value = value;
}
/// <summary>
/// Breakeven activation distance in points.
/// </summary>
public int BreakEvenStep
{
get => _breakEvenStep.Value;
set => _breakEvenStep.Value = value;
}
/// <summary>
/// Trailing stop distance in points.
/// </summary>
public int TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Trailing step distance in points.
/// </summary>
public int TrailingStep
{
get => _trailingStep.Value;
set => _trailingStep.Value = value;
}
/// <summary>
/// Trading window start time (HH:mm).
/// </summary>
public string StartTime
{
get => _startTime.Value;
set => _startTime.Value = value;
}
/// <summary>
/// Trading window end time (HH:mm).
/// </summary>
public string EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>
/// Initialize a new instance of <see cref="GridEaProStrategy"/>.
/// </summary>
public GridEaProStrategy()
{
_mode = Param(nameof(Mode), GridTradeModes.Both)
.SetDisplay("Mode", "Allowed trade direction", "General");
_entryMode = Param(nameof(EntryMode), GridEntryModes.Rsi)
.SetDisplay("Entry Mode", "Signal source", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Lookback for RSI signal", "RSI")
;
_rsiUpperLevel = Param(nameof(RsiUpperLevel), 70m)
.SetDisplay("RSI Upper", "Overbought threshold", "RSI")
;
_rsiLowerLevel = Param(nameof(RsiLowerLevel), 30m)
.SetDisplay("RSI Lower", "Oversold threshold", "RSI")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for signals", "General");
_distance = Param(nameof(Distance), 50)
.SetDisplay("Distance", "Breakout distance in points", "Entries")
;
_timerSeconds = Param(nameof(TimerSeconds), 10)
.SetDisplay("Timer Seconds", "Interval between breakout recalculations", "Entries");
_initialVolume = Param(nameof(InitialVolume), 0.01m)
.SetDisplay("Initial Volume", "Base order volume", "Money Management");
_fromBalance = Param(nameof(FromBalance), 1000m)
.SetDisplay("Balance Divider", "Reference balance for lot calculation", "Money Management");
_riskPerTrade = Param(nameof(RiskPerTrade), 0m)
.SetDisplay("Risk %", "Risk per trade percentage", "Money Management")
;
_lotMultiplier = Param(nameof(LotMultiplier), 1.1m)
.SetDisplay("Lot Multiplier", "Multiplier applied to grid additions", "Money Management");
_maxLot = Param(nameof(MaxLot), 999.9m)
.SetDisplay("Max Lot", "Upper cap for volume", "Money Management");
_stepOrders = Param(nameof(StepOrders), 100)
.SetDisplay("Step", "Base grid distance in points", "Grid")
;
_stepMultiplier = Param(nameof(StepMultiplier), 1.1m)
.SetDisplay("Step Multiplier", "Adaptive grid expansion factor", "Grid");
_maxStep = Param(nameof(MaxStep), 1000)
.SetDisplay("Max Step", "Maximum grid step in points", "Grid");
_overlapOrders = Param(nameof(OverlapOrders), 5)
.SetDisplay("Overlap Orders", "Required orders for overlap exit", "Grid");
_overlapPips = Param(nameof(OverlapPips), 10)
.SetDisplay("Overlap Pips", "Offset used by overlap exit", "Grid");
_stopLoss = Param(nameof(StopLoss), -1)
.SetDisplay("Stop Loss", "Initial stop in points (-1 disables)", "Risk")
;
_takeProfit = Param(nameof(TakeProfit), 500)
.SetDisplay("Take Profit", "Initial profit target in points", "Risk")
;
_breakEvenStop = Param(nameof(BreakEvenStop), -1)
.SetDisplay("Break Even Stop", "Offset once breakeven triggers (-1 disables)", "Risk");
_breakEvenStep = Param(nameof(BreakEvenStep), 10)
.SetDisplay("Break Even Step", "Distance to activate breakeven", "Risk");
_trailingStop = Param(nameof(TrailingStop), 50)
.SetDisplay("Trailing Stop", "Trailing distance in points", "Risk");
_trailingStep = Param(nameof(TrailingStep), 50)
.SetDisplay("Trailing Step", "Minimum move before trailing update", "Risk");
_startTime = Param(nameof(StartTime), "00:00")
.SetDisplay("Start Time", "Trading window start (HH:mm)", "Sessions");
_endTime = Param(nameof(EndTime), "00:00")
.SetDisplay("End Time", "Trading window end (HH:mm)", "Sessions");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_longVolumes.Clear();
_shortVolumes.Clear();
_rsi = null!;
_tickSize = 0m;
_stepValue = 0m;
_tickValue = 0m;
_lastLongPrice = 0m;
_lastShortPrice = 0m;
_lastLongVolume = 0m;
_lastShortVolume = 0m;
_longStop = null;
_shortStop = null;
_longTake = null;
_shortTake = null;
_longBreakEven = false;
_shortBreakEven = false;
_longTrailAnchor = 0m;
_shortTrailAnchor = 0m;
_longNextLevel = null;
_shortNextLevel = null;
_pendingLong = null;
_pendingShort = null;
_nextTimer = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_tickSize = Security?.PriceStep ?? 0.0001m;
_stepValue = _tickSize;
_tickValue = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 1m;
Volume = InitialVolume;
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
StartProtection(null, null);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!IsWithinTradingHours(candle.CloseTime))
return;
ManageExits(candle);
HandleEntries(candle, rsi);
ProcessPendingBreakouts(candle);
ProcessGridExpansions(candle);
}
private void HandleEntries(ICandleMessage candle, decimal rsi)
{
switch (EntryMode)
{
case GridEntryModes.Rsi:
HandleRsiEntries(candle, rsi);
break;
case GridEntryModes.FixedPoints:
ScheduleBreakoutLevels(candle);
break;
case GridEntryModes.Manual:
break;
}
}
private void HandleRsiEntries(ICandleMessage candle, decimal rsi)
{
if (AllowShortEntries() && _shortVolumes.Count == 0 && rsi >= RsiUpperLevel)
EnterShort(candle.ClosePrice);
if (AllowLongEntries() && _longVolumes.Count == 0 && rsi <= RsiLowerLevel)
EnterLong(candle.ClosePrice);
}
private void ScheduleBreakoutLevels(ICandleMessage candle)
{
if (TimerSeconds <= 0)
{
PrepareBreakoutLevels(candle);
return;
}
if (_nextTimer == null)
_nextTimer = candle.CloseTime.AddSeconds(TimerSeconds);
if (candle.CloseTime >= _nextTimer)
{
PrepareBreakoutLevels(candle);
_nextTimer = candle.CloseTime.AddSeconds(TimerSeconds);
}
}
private void PrepareBreakoutLevels(ICandleMessage candle)
{
var offset = Distance * _stepValue;
if (AllowLongEntries() && _longVolumes.Count == 0)
{
_pendingLong = candle.ClosePrice + offset;
}
if (AllowShortEntries() && _shortVolumes.Count == 0)
{
_pendingShort = candle.ClosePrice - offset;
}
}
private void ProcessPendingBreakouts(ICandleMessage candle)
{
if (_pendingLong.HasValue && AllowLongEntries())
{
if (candle.HighPrice >= _pendingLong.Value)
{
EnterLong(_pendingLong.Value);
_pendingLong = null;
}
}
if (_pendingShort.HasValue && AllowShortEntries())
{
if (candle.LowPrice <= _pendingShort.Value)
{
EnterShort(_pendingShort.Value);
_pendingShort = null;
}
}
}
private void ProcessGridExpansions(ICandleMessage candle)
{
if (_longVolumes.Count > 0 && _longNextLevel.HasValue && AllowLongEntries())
{
if (candle.LowPrice <= _longNextLevel.Value)
{
EnterLong(_longNextLevel.Value);
_longNextLevel = null;
}
}
if (_shortVolumes.Count > 0 && _shortNextLevel.HasValue && AllowShortEntries())
{
if (candle.HighPrice >= _shortNextLevel.Value)
{
EnterShort(_shortNextLevel.Value);
_shortNextLevel = null;
}
}
}
private void ManageExits(ICandleMessage candle)
{
if (_longVolumes.Count > 0 && Position > 0)
{
UpdateLongRisk(candle);
if (_longTake.HasValue && candle.HighPrice >= _longTake.Value)
{
CloseLong();
return;
}
if (_longStop.HasValue && candle.LowPrice <= _longStop.Value)
{
CloseLong();
return;
}
}
if (_shortVolumes.Count > 0 && Position < 0)
{
UpdateShortRisk(candle);
if (_shortTake.HasValue && candle.LowPrice <= _shortTake.Value)
{
CloseShort();
return;
}
if (_shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
{
CloseShort();
}
}
}
private void EnterLong(decimal referencePrice)
{
var volume = CalculateNextLongVolume();
if (volume <= 0m)
return;
BuyMarket(volume);
_lastLongPrice = referencePrice;
_lastLongVolume = volume;
}
private void EnterShort(decimal referencePrice)
{
var volume = CalculateNextShortVolume();
if (volume <= 0m)
return;
SellMarket(volume);
_lastShortPrice = referencePrice;
_lastShortVolume = volume;
}
private decimal CalculateNextLongVolume()
{
var volume = CalculateBaseVolume();
if (_longVolumes.Count > 0 && LotMultiplier > 1m)
{
var lastVolume = _longVolumes[_longVolumes.Count - 1];
volume = Math.Min(lastVolume * LotMultiplier, MaxLot);
}
return Math.Min(volume, MaxLot);
}
private decimal CalculateNextShortVolume()
{
var volume = CalculateBaseVolume();
if (_shortVolumes.Count > 0 && LotMultiplier > 1m)
{
var lastVolume = _shortVolumes[_shortVolumes.Count - 1];
volume = Math.Min(lastVolume * LotMultiplier, MaxLot);
}
return Math.Min(volume, MaxLot);
}
private decimal CalculateBaseVolume()
{
var volume = InitialVolume;
if (RiskPerTrade > 0m && StopLoss > 0 && _tickValue > 0m)
{
var balance = Portfolio?.CurrentValue ?? 0m;
var riskAmount = balance * RiskPerTrade / 100m;
var stopDistance = StopLoss * _stepValue;
if (stopDistance > 0m)
{
var valuePerLot = (_tickValue * stopDistance) / _tickSize;
if (valuePerLot > 0m)
volume = Math.Max(volume, riskAmount / valuePerLot);
}
}
else if (FromBalance > 0m)
{
var balance = Portfolio?.CurrentValue ?? 0m;
if (balance > 0m)
volume = Math.Max(volume, balance / FromBalance * InitialVolume);
}
return Math.Max(volume, 0m);
}
private void UpdateLongRisk(ICandleMessage candle)
{
var stopDistance = StopLoss > 0 ? StopLoss * _stepValue : 0m;
var takeDistance = TakeProfit > 0 ? TakeProfit * _stepValue : 0m;
if (!_longTake.HasValue && takeDistance > 0m)
_longTake = _lastLongPrice + takeDistance;
if (!_longStop.HasValue && stopDistance > 0m)
_longStop = _lastLongPrice - stopDistance;
var breakEvenDistance = BreakEvenStep > 0 ? BreakEvenStep * _stepValue : 0m;
var breakEvenOffset = BreakEvenStop >= 0 ? BreakEvenStop * _stepValue : 0m;
if (!_longBreakEven && breakEvenDistance > 0m)
{
if (candle.HighPrice - _lastLongPrice >= breakEvenDistance)
{
_longBreakEven = true;
var breakEvenPrice = _lastLongPrice + breakEvenOffset;
if (!_longStop.HasValue || _longStop.Value < breakEvenPrice)
_longStop = breakEvenPrice;
}
}
var trailingDistance = TrailingStop > 0 ? TrailingStop * _stepValue : 0m;
var trailingStep = TrailingStep > 0 ? TrailingStep * _stepValue : trailingDistance;
if (trailingDistance > 0m)
{
if (_longTrailAnchor == 0m)
_longTrailAnchor = _lastLongPrice;
if (candle.HighPrice - _longTrailAnchor >= trailingStep)
{
_longTrailAnchor = candle.HighPrice;
var trailStop = candle.HighPrice - trailingDistance;
if (!_longStop.HasValue || _longStop.Value < trailStop)
_longStop = trailStop;
}
}
}
private void UpdateShortRisk(ICandleMessage candle)
{
var stopDistance = StopLoss > 0 ? StopLoss * _stepValue : 0m;
var takeDistance = TakeProfit > 0 ? TakeProfit * _stepValue : 0m;
if (!_shortTake.HasValue && takeDistance > 0m)
_shortTake = _lastShortPrice - takeDistance;
if (!_shortStop.HasValue && stopDistance > 0m)
_shortStop = _lastShortPrice + stopDistance;
var breakEvenDistance = BreakEvenStep > 0 ? BreakEvenStep * _stepValue : 0m;
var breakEvenOffset = BreakEvenStop >= 0 ? BreakEvenStop * _stepValue : 0m;
if (!_shortBreakEven && breakEvenDistance > 0m)
{
if (_lastShortPrice - candle.LowPrice >= breakEvenDistance)
{
_shortBreakEven = true;
var breakEvenPrice = _lastShortPrice - breakEvenOffset;
if (!_shortStop.HasValue || _shortStop.Value > breakEvenPrice)
_shortStop = breakEvenPrice;
}
}
var trailingDistance = TrailingStop > 0 ? TrailingStop * _stepValue : 0m;
var trailingStep = TrailingStep > 0 ? TrailingStep * _stepValue : trailingDistance;
if (trailingDistance > 0m)
{
if (_shortTrailAnchor == 0m)
_shortTrailAnchor = _lastShortPrice;
if (_shortTrailAnchor - candle.LowPrice >= trailingStep)
{
_shortTrailAnchor = candle.LowPrice;
var trailStop = candle.LowPrice + trailingDistance;
if (!_shortStop.HasValue || _shortStop.Value > trailStop)
_shortStop = trailStop;
}
}
}
private void CloseLong()
{
if (Position <= 0)
return;
SellMarket(Position);
ResetLongState();
}
private void CloseShort()
{
if (Position >= 0)
return;
BuyMarket(Math.Abs(Position));
ResetShortState();
}
private void ResetLongState()
{
_longVolumes.Clear();
_longStop = null;
_longTake = null;
_longBreakEven = false;
_longTrailAnchor = 0m;
_longNextLevel = null;
_pendingLong = null;
}
private void ResetShortState()
{
_shortVolumes.Clear();
_shortStop = null;
_shortTake = null;
_shortBreakEven = false;
_shortTrailAnchor = 0m;
_shortNextLevel = null;
_pendingShort = null;
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
var order = trade.Order;
if (order == null)
return;
if (order.Side == Sides.Buy)
{
if (Position > 0)
{
_longVolumes.Add(trade.Trade.Volume);
_lastLongPrice = trade.Trade.Price;
_lastLongVolume = trade.Trade.Volume;
ResetShortState();
RecalculateLongLevels();
}
else
{
ResetShortState();
}
}
else if (order.Side == Sides.Sell)
{
if (Position < 0)
{
_shortVolumes.Add(trade.Trade.Volume);
_lastShortPrice = trade.Trade.Price;
_lastShortVolume = trade.Trade.Volume;
ResetLongState();
RecalculateShortLevels();
}
else
{
ResetLongState();
}
}
}
private void RecalculateLongLevels()
{
_longStop = StopLoss > 0 ? _lastLongPrice - StopLoss * _stepValue : null;
_longTake = TakeProfit > 0 ? _lastLongPrice + TakeProfit * _stepValue : null;
_longBreakEven = false;
_longTrailAnchor = _lastLongPrice;
_longNextLevel = ComputeNextLongLevel();
}
private void RecalculateShortLevels()
{
_shortStop = StopLoss > 0 ? _lastShortPrice + StopLoss * _stepValue : null;
_shortTake = TakeProfit > 0 ? _lastShortPrice - TakeProfit * _stepValue : null;
_shortBreakEven = false;
_shortTrailAnchor = _lastShortPrice;
_shortNextLevel = ComputeNextShortLevel();
}
private decimal? ComputeNextLongLevel()
{
if (StepOrders <= 0)
return null;
var step = CalculateAdaptiveStep(_longVolumes.Count);
return _lastLongPrice - step;
}
private decimal? ComputeNextShortLevel()
{
if (StepOrders <= 0)
return null;
var step = CalculateAdaptiveStep(_shortVolumes.Count);
return _lastShortPrice + step;
}
private decimal CalculateAdaptiveStep(int ordersCount)
{
var baseStep = Math.Min(StepOrders, MaxStep);
var step = (decimal)baseStep * _stepValue;
if (StepMultiplier > 1m && ordersCount > 1)
{
var scaled = Math.Min(MaxStep, (int)Math.Round((ordersCount - 1) * StepMultiplier * StepOrders, MidpointRounding.AwayFromZero));
step = scaled * _stepValue;
}
return step;
}
private bool AllowLongEntries()
{
return Mode == GridTradeModes.Both || Mode == GridTradeModes.Buy;
}
private bool AllowShortEntries()
{
return Mode == GridTradeModes.Both || Mode == GridTradeModes.Sell;
}
private bool IsWithinTradingHours(DateTimeOffset time)
{
if (StartTime.EqualsIgnoreCase("00:00") && EndTime.EqualsIgnoreCase("00:00"))
return true;
if (!TimeSpan.TryParseExact(StartTime, "hh\\:mm", CultureInfo.InvariantCulture, out var start))
return true;
if (!TimeSpan.TryParseExact(EndTime, "hh\\:mm", CultureInfo.InvariantCulture, out var end))
return true;
var current = time.TimeOfDay;
if (start == end)
return true;
var min = start < end ? start : end;
var max = start > end ? start : end;
var inRange = current >= min && current < max;
return start <= end ? inRange : !inRange;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class grid_ea_pro_strategy(Strategy):
def __init__(self):
super(grid_ea_pro_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._rsi_period = self.Param("RsiPeriod", 10)
self._rsi_upper_level = self.Param("RsiUpperLevel", 70.0)
self._rsi_lower_level = self.Param("RsiLowerLevel", 30.0)
self._take_profit_pct = self.Param("TakeProfitPct", 1.0)
self._stop_loss_pct = self.Param("StopLossPct", 0.5)
self._entry_price = 0.0
self._direction = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiUpperLevel(self):
return self._rsi_upper_level.Value
@RsiUpperLevel.setter
def RsiUpperLevel(self, value):
self._rsi_upper_level.Value = value
@property
def RsiLowerLevel(self):
return self._rsi_lower_level.Value
@RsiLowerLevel.setter
def RsiLowerLevel(self, value):
self._rsi_lower_level.Value = value
@property
def TakeProfitPct(self):
return self._take_profit_pct.Value
@TakeProfitPct.setter
def TakeProfitPct(self, value):
self._take_profit_pct.Value = value
@property
def StopLossPct(self):
return self._stop_loss_pct.Value
@StopLossPct.setter
def StopLossPct(self, value):
self._stop_loss_pct.Value = value
def OnReseted(self):
super(grid_ea_pro_strategy, self).OnReseted()
self._entry_price = 0.0
self._direction = 0
def OnStarted2(self, time):
super(grid_ea_pro_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._direction = 0
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._process_candle).Start()
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
close = float(candle.ClosePrice)
tp_pct = float(self.TakeProfitPct)
sl_pct = float(self.StopLossPct)
# Check exit
if self.Position != 0 and self._entry_price > 0:
if self._direction > 0:
pnl = (close - self._entry_price) / self._entry_price * 100.0
if pnl >= tp_pct or pnl <= -sl_pct:
self.SellMarket()
self._entry_price = 0.0
self._direction = 0
return
elif self._direction < 0:
pnl = (self._entry_price - close) / self._entry_price * 100.0
if pnl >= tp_pct or pnl <= -sl_pct:
self.BuyMarket()
self._entry_price = 0.0
self._direction = 0
return
# RSI entry
if self.Position == 0:
if rsi_val <= float(self.RsiLowerLevel):
self.BuyMarket()
self._entry_price = close
self._direction = 1
elif rsi_val >= float(self.RsiUpperLevel):
self.SellMarket()
self._entry_price = close
self._direction = -1
def CreateClone(self):
return grid_ea_pro_strategy()