Перенос экспертного советника MetaTrader 4 SimpleTrade.mq4 (в исходнике также встречается название «neroTrade») на платформу StockSharp.
Работает с одним инструментом и временным интервалом, задаваемым параметром CandleType.
В любой момент времени держит не более одной позиции и на открытии каждой новой свечи переходит в нужное направление.
Логика торговли
При появлении новой свечи в состоянии Active стратегия считывает её цену открытия и сравнивает с ценой открытия свечи, находящейся на LookbackBars периодов левее.
Если текущая цена открытия выше эталонной, все позиции закрываются и отправляется рыночная покупка объёмом TradeVolume лотов.
Если текущая цена открытия ниже или равна эталонной, открытые позиции также закрываются и выполняется рыночная продажа тем же объёмом.
Параметр StopLossPoints полностью повторяет поле stop из MQL-версии. При наличии у инструмента PriceStep значение переводится в абсолютные цены и передаётся в StartProtection, что позволяет StockSharp автоматически сопровождать стоп-лосс.
История цен открытия накапливается через высокоуровневую подписку на свечи: завершённые свечи пополняют буфер, а активная свеча инициирует расчёт ровно один раз за бар.
Параметры
Параметр
Назначение
Значение по умолчанию
TradeVolume
Объём заявки в лотах. Должен быть больше нуля.
1
StopLossPoints
Дистанция защитного стоп-лосса в пунктах инструмента. Значение 0 отключает автоматический стоп.
120
LookbackBars
Количество свечей для сравнения цен открытия. При 3 реализуется проверка Open[0] против Open[3].
3
CandleType
Тип данных (временной интервал) для запрашиваемых свечей. Определяет частоту появления сигналов.
таймфрейм 1 час
Особенности реализации
Задействован высокоуровневый пайплайн SubscribeCandles(...).Bind(...), что обеспечивает одинаковое поведение в бэктесте и в онлайне.
В OnStarted вызывается StartProtection. Убедитесь, что у инструмента задан PriceStep; без него рассчитать абсолютный стоп-лосс невозможно.
Все сделки совершаются рыночными ордерами на открытии свечей, поэтому отдельный параметр проскальзывания не нужен.
Буфер цен открытия ограничен LookbackBars + 5 элементами и не разрастается бесконтрольно.
В папке присутствует только реализация на C#; версия на Python не создавалась.
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>
/// StockSharp port of the MetaTrader "SimpleTrade" expert advisor.
/// Compares the opening price of the current bar with the bar from several periods ago and flips the position accordingly.
/// </summary>
public class SimpleTradeFlipStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<int> _lookbackBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _openHistory = new();
private int _cooldown;
/// <summary>
/// Initializes a new instance of the <see cref="SimpleTradeFlipStrategy"/> class.
/// </summary>
public SimpleTradeFlipStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order size in lots", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 120m)
.SetNotNegative()
.SetDisplay("Stop-Loss Points", "Protective stop distance expressed in instrument points", "Risk");
_lookbackBars = Param(nameof(LookbackBars), 10)
.SetGreaterThanZero()
.SetDisplay("Lookback Bars", "Number of bars used for the open price comparison", "Signals");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe used for signal calculations", "General");
}
/// <summary>
/// Order size submitted with each entry.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Stop-loss distance in instrument points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = Math.Max(0m, value);
}
/// <summary>
/// Number of historical bars used for the open price comparison.
/// </summary>
public int LookbackBars
{
get => _lookbackBars.Value;
set => _lookbackBars.Value = Math.Max(1, value);
}
/// <summary>
/// Candle type that defines the working timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_openHistory.Clear();
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 0m;
Unit stopLossUnit = null;
if (StopLossPoints > 0m && step > 0m)
stopLossUnit = new Unit(StopLossPoints * step, UnitTypes.Absolute);
StartProtection(null, stopLossUnit);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Store the open price for future comparisons.
_openHistory.Add(candle.OpenPrice);
var maxHistory = Math.Max(LookbackBars + 5, 5);
if (_openHistory.Count > maxHistory)
_openHistory.RemoveRange(0, _openHistory.Count - maxHistory);
var lookback = LookbackBars;
if (_openHistory.Count <= lookback)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var volume = TradeVolume;
if (volume <= 0m)
return;
var currentOpen = candle.OpenPrice;
var referenceOpen = _openHistory[^(lookback + 1)];
// Only trade on clear directional difference
var diff = currentOpen - referenceOpen;
if (Math.Abs(diff) < currentOpen * 0.001m)
return;
if (diff > 0 && Position <= 0)
{
if (Position < 0m)
BuyMarket(Math.Abs(Position));
BuyMarket(volume);
_cooldown = 5;
}
else if (diff < 0 && Position >= 0)
{
if (Position > 0m)
SellMarket(Position);
SellMarket(volume);
_cooldown = 5;
}
}
}
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, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
class simple_trade_flip_strategy(Strategy):
def __init__(self):
super(simple_trade_flip_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 1.0) \
.SetDisplay("Trade Volume", "Order size in lots", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 120.0) \
.SetDisplay("Stop-Loss Points", "Protective stop distance in instrument points", "Risk")
self._lookback_bars = self.Param("LookbackBars", 10) \
.SetDisplay("Lookback Bars", "Number of bars used for open price comparison", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Primary timeframe used for signal calculations", "General")
self._open_history = []
self._cooldown = 0
@property
def TradeVolume(self):
return self._trade_volume.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def LookbackBars(self):
return self._lookback_bars.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(simple_trade_flip_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
ps = self.Security.PriceStep if self.Security is not None else None
step = float(ps) if ps is not None and float(ps) > 0 else 0.0
sl_pts = float(self.StopLossPoints)
sl = Unit(sl_pts * step, UnitTypes.Absolute) if sl_pts > 0 and step > 0 else None
self.StartProtection(None, sl)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self._open_history.append(float(candle.OpenPrice))
max_history = max(self.LookbackBars + 5, 5)
if len(self._open_history) > max_history:
self._open_history = self._open_history[-max_history:]
lookback = int(self.LookbackBars)
if len(self._open_history) <= lookback:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown > 0:
self._cooldown -= 1
return
volume = float(self.TradeVolume)
if volume <= 0:
return
current_open = float(candle.OpenPrice)
reference_open = self._open_history[-(lookback + 1)]
diff = current_open - reference_open
if abs(diff) < current_open * 0.001:
return
if diff > 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(volume)
self._cooldown = 5
elif diff < 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
self.SellMarket(volume)
self._cooldown = 5
def OnReseted(self):
super(simple_trade_flip_strategy, self).OnReseted()
self._open_history = []
self._cooldown = 0
def CreateClone(self):
return simple_trade_flip_strategy()