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 StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simple trade strategy converted from MetaTrader that compares the current open with the open three bars ago.
/// The logic holds positions for a single bar and protects the trade with a fixed stop distance.
/// </summary>
public class SimpleTradeStrategy : Strategy
{
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal _openCurrent;
private decimal _openMinus1;
private decimal _openMinus2;
private decimal _openMinus3;
private int _historyCount;
private decimal? _stopPrice;
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Trade volume used for market orders.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="SimpleTradeStrategy"/> parameters.
/// </summary>
public SimpleTradeStrategy()
{
_stopLossPips = Param(nameof(StopLossPips), 120)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Fixed protective distance in pips", "Risk Management")
;
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume in lots", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle source for decisions", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_openCurrent = 0m;
_openMinus1 = 0m;
_openMinus2 = 0m;
_openMinus3 = 0m;
_historyCount = 0;
_stopPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Exit existing trades before evaluating new entries to mimic the original MQL behaviour.
if (TryCloseExistingPosition(candle))
return;
UpdateHistory(candle.OpenPrice);
// Need at least four opens to compare with the value three bars ago.
if (_historyCount < 4)
return;
ExecuteEntry(candle);
}
private bool TryCloseExistingPosition(ICandleMessage candle)
{
if (Position > 0)
{
var volume = Position;
// Close long trades at the protective stop or at the bar change.
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
}
else
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
}
_stopPrice = null;
return true;
}
if (Position < 0)
{
var volume = Math.Abs(Position);
// Close short trades at the protective stop or at the bar change.
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
}
else
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
}
_stopPrice = null;
return true;
}
return false;
}
private void UpdateHistory(decimal currentOpen)
{
// Shift the stored opens so that _openMinus3 keeps the value three candles back.
_openMinus3 = _openMinus2;
_openMinus2 = _openMinus1;
_openMinus1 = _openCurrent;
_openCurrent = currentOpen;
if (_historyCount < 4)
_historyCount++;
}
private void ExecuteEntry(ICandleMessage candle)
{
var pipSize = CalculatePipSize();
var stopOffset = pipSize * StopLossPips;
// Enter long when the current open is above the open three bars ago, otherwise enter short.
if (_openCurrent > _openMinus3)
{
BuyMarket();
_stopPrice = candle.OpenPrice - stopOffset;
}
else
{
SellMarket();
_stopPrice = candle.OpenPrice + stopOffset;
}
}
private decimal CalculatePipSize()
{
// Reproduce the MetaTrader adjustment: multiply by ten for symbols with 3 or 5 decimals.
var step = Security?.PriceStep ?? 1m;
var decimals = Security?.Decimals;
if (decimals == 3 || decimals == 5)
step *= 10m;
return step;
}
}
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.Strategies import Strategy
class simple_trade_strategy(Strategy):
def __init__(self):
super(simple_trade_strategy, self).__init__()
self._sl_pips = self.Param("StopLossPips", 120)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(simple_trade_strategy, self).OnReseted()
self._opens = []
self._stop_price = None
def OnStarted2(self, time):
super(simple_trade_strategy, self).OnStarted2(time)
self._opens = []
self._stop_price = None
self._pip_size = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
self._pip_size = float(self.Security.PriceStep)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
# Close existing position first (hold only 1 bar)
if self.Position > 0:
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket()
else:
self.SellMarket()
self._stop_price = None
self._update_opens(float(candle.OpenPrice))
return
elif self.Position < 0:
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket()
else:
self.BuyMarket()
self._stop_price = None
self._update_opens(float(candle.OpenPrice))
return
self._update_opens(float(candle.OpenPrice))
if len(self._opens) < 4:
return
open_current = self._opens[-1]
open_minus3 = self._opens[-4]
stop_offset = self._pip_size * self._sl_pips.Value
if open_current > open_minus3:
self.BuyMarket()
self._stop_price = float(candle.OpenPrice) - stop_offset
else:
self.SellMarket()
self._stop_price = float(candle.OpenPrice) + stop_offset
def _update_opens(self, val):
self._opens.append(val)
if len(self._opens) > 5:
self._opens.pop(0)
def CreateClone(self):
return simple_trade_strategy()