Эта стратегия переносит советник 1 MINUTE SCALPER из MetaTrader 4 на высокоуровневый API StockSharp. Логика сохраняет многоуровневую фильтрацию тренда, подтверждение импульса на старшем таймфрейме и месячный фильтр MACD, но управление риском адаптировано под модель неттинговых позиций StockSharp.
Основная логика
Каскад LWMA – тринадцать линейно-взвешенных средних (LWMA 3/5/8/10/12/15/30/35/40/45/50/55/200) должны быть строго упорядочены. Для лонга каждая быстрая средняя расположена выше следующей, для шорта условия инвертируются.
Базовый тренд-фильтр – дополнительная быстрая LWMA (по умолчанию 6) должна находиться выше медленной LWMA (85) в лонге и ниже в шорте, как и в исходном скрипте.
Структура свечей – сигнал формируется только при наличии перекрытий из исходного кода: для покупки минимум позапрошлой свечи должен быть ниже максимума прошлой, для продажи минимум прошлой свечи должен опускаться ниже максимума двух свечей назад.
Импульсный фильтр – индикатор Momentum длиной 14 на более крупном таймфрейме (по умолчанию 15-минутные свечи) должен отклониться от уровня 100 как минимум на величину заданных порогов на любом из трех последних значений. Это повторяет проверки MomLevelB/MomLevelS.
Месячный MACD – MACD на выбранном таймфрейме (по умолчанию 30-дневные свечи, приблизительный аналог месяца) должен показывать, что основная линия выше сигнальной для лонгов и ниже для шортов.
Управление сделкой
Исходные уровни – стоп-лосс и тейк-профит задаются в шагах цены. После открытия позиция получает абсолютные значения с учётом Security.PriceStep.
Переход в безубыток – при движении цены на BreakEvenTriggerSteps в прибыльную сторону стоп переносится к цене входа с дополнительным отступом BreakEvenOffsetSteps (для шорта условие зеркально).
Ступенчатый трейлинг – при положительном TrailingStopSteps стоп следует за экстремумом с расстоянием, выраженным в шагах.
Денежный трейлинг – когда плавающий результат превышает MoneyTrailTarget, стратегия отслеживает максимальную прибыль и закрывает позицию, если откат достигает MoneyTrailStop.
Денежные/процентные цели – опциональные фиксаторы закрывают позицию при достижении абсолютного или процентного порога плавающей прибыли. Процент вычисляется от стоимости портфеля на момент старта.
Эквити-стоп – фиксируется максимум совокупного капитала (стоимость портфеля плюс нереализованная прибыль). Если просадка превышает EquityRiskPercent, позиция закрывается, как в функции AccountEquityHigh() оригинального советника.
Параметры
Параметр
Описание
Volume
Объём заявки. При смене направления к нему прибавляется текущая абсолютная позиция, чтобы сразу развернуться.
FastMaPeriod / SlowMaPeriod
Периоды LWMA основного тренд-фильтра.
MomentumPeriod
Период индикатора Momentum на старшем таймфрейме.
MomentumBuyThreshold / MomentumSellThreshold
Минимальное отклонение от уровня 100 для подтверждения импульса в лонг/шорт.
Таймфрейм для MACD (по умолчанию 30 дней ≈ месяц).
Отличия от MT4-версии
StockSharp использует неттинг, поэтому стратегия удерживает только одну агрегированную позицию вместо пачки ордеров (Max_Trades). Разворот закрывает текущую позицию и сразу открывает обратную.
Параметр PercentTakeProfit опирается на стоимость портфеля в момент старта, а не на постоянно меняющийся AccountBalance() MetaTrader. Это исключает ложные срабатывания при внешних операциях по счёту.
Денежные правила выхода (Take_Profit_In_Money, TRAIL_PROFIT_IN_MONEY2) рассчитываются по плавающей прибыли от средней цены позиции, что соответствует оригиналу, но вписано в инфраструктуру StockSharp.
Необходимо обеспечить поставку свечей всех выбранных таймфреймов (CandleType, MomentumCandleType, MacdCandleType). Убедитесь, что подключённый адаптер поддерживает эти интервалы.
Подбирайте параметры под волатильность инструмента. Для «шумных» рынков стоит увеличить шаги стопов и пороги импульса, чтобы снизить число ложных входов.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// One-minute scalper strategy using fast/slow WMA crossover.
/// Buys on bullish crossover, sells on bearish crossover.
/// </summary>
public class OneMinuteScalperStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast WMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow WMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="OneMinuteScalperStrategy"/> class.
/// </summary>
public OneMinuteScalperStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 85)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow WMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// WMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 60;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 60;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class one_minute_scalper_strategy(Strategy):
def __init__(self):
super(one_minute_scalper_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20).SetGreaterThanZero().SetDisplay("Fast Period", "Fast WMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 85).SetGreaterThanZero().SetDisplay("Slow Period", "Slow WMA period", "Indicator")
self._sl_points = self.Param("StopLossPoints", 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 500).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
def OnReseted(self):
super(one_minute_scalper_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
def OnStarted2(self, time):
super(one_minute_scalper_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
self._fast = WeightedMovingAverage()
self._fast.Length = self._fast_period.Value
self._slow = WeightedMovingAverage()
self._slow.Length = self._slow_period.Value
sub = self.SubscribeCandles(tf(5))
sub.Bind(self._fast, self._slow, self.OnProcess).Start()
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = candle.ClosePrice
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
step = float(self.Security.PriceStep)
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# WMA crossover
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 60
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return one_minute_scalper_strategy()