Стратегия представляет собой полную конвертацию эксперта MetaTrader Proffessor v3
на высокоуровневый API StockSharp. Сохранена исходная идея: определять режим рынка
по ADX и строить сетку защитных и усредняющих заявок вокруг текущей позиции.
Индикатор: Average Directional Index (ADX) с периодом 14 и линиями +DI/-DI.
Режимы: флэт (ADX ниже порога) и тренд (ADX выше порога).
Заявки: открывает рыночную позицию и выставляет симметричную сетку
отложенных приказов для хеджирования, пирамидинга или среднения.
Выход: закрывает все позиции и отложенные заявки при достижении заданной
прибыли или убытка.
Сеанс: торгует только в пределах выбранного диапазона часов.
Логика торговли
Определение режима
Подписка на выбранный тип свечей и расчёт ADX.
Задержка сигнала ADX на указанное количество закрытых свечей (BarOffset),
аналогично CopyBuffer(handle, shift) в MQL.
При отсутствии позиции анализируется последняя отложенная величина ADX:
Флэт, быки: ADX < AdxFlatLevel и +DI > -DI.
Флэт, медведи: ADX < AdxFlatLevel и +DI < -DI.
Тренд вверх: ADX ≥ AdxFlatLevel и +DI > -DI.
Тренд вниз: ADX ≥ AdxFlatLevel и +DI < -DI.
Расстановка заявок
После рыночного входа базовым объёмом создаётся симметричная сетка вокруг цены.
Расстояния выражены в пунктах точь-в-точь как в MQL и автоматически масштабируются
на размер шага цены инструмента.
Флэт, быки: длинная позиция, защитный sell-stop, buy limit ниже ask и
sell limit выше bid для игры в коридоре.
Флэт, медведи: короткая позиция, защитный buy-stop, buy limit для
перекрытия короткой позиции и sell limit выше для перевхода.
Тренд вверх: длинная позиция, sell-stop для хеджирования и buy-stop для
пирамидинга по пробою.
Тренд вниз: короткая позиция, sell-stop по тренду и buy-stop против резкого
разворота.
Шаг сетки рассчитывается по формуле оригинала: каждое новое колено добавляет
GridStep + GridDeltaIncrement * level / 2. Объём ордеров корректируется через
LotMultiplier и LotAddition, затем нормализуется по шагу и границам объёма
инструмента.
Управление выходом
Нереализованная прибыль вычисляется по средней цене позиции и закрытию
последней свечи.
При превышении ProfitTarget или падении ниже LossLimit (если он не ноль)
стратегия закрывает позицию и отменяет все отложенные заявки.
Вне интервала [StartHour, EndHour) сделки не совершаются, как и в функции
Time() оригинала.
Особенности реализации
Bid/ask для отложенных приказов приближаются как цена закрытия плюс/минус
половина шага цены, что позволяет перенести тиковую логику на свечные данные.
Пункт (point) пересчитывается по шагу цены и корректируется для инструментов
с тремя и пятью знаками после запятой, как в переменной m_adjusted_point.
Объёмы и цены нормализуются с учётом шагов, минимумов и максимумов биржи перед
отправкой заявок.
Обработка ведётся только по завершённым свечам, чтобы исключить ложные сигналы.
Параметры
Параметр
Описание
Volume
Базовый объём рыночного ордера.
LotMultiplier
Множитель для объёма отложенных заявок.
LotAddition
Дополнительный объём после применения множителя.
MaxLevels
Максимальное число уровней сетки на сторону.
GridDeltaIncrement
Прибавка к шагу сетки для дальних уровней (в пунктах).
GridInitialOffset
Расстояние до первой защитной заявки (в пунктах).
GridStep
Базовый шаг между соседними уровнями (в пунктах).
ProfitTarget
Уровень прибыли, при котором закрываются все позиции.
LossLimit
Уровень убытка, инициирующий закрытие (0 — отключено).
AdxFlatLevel
Порог ADX, разделяющий флэт и тренд.
BarOffset
Число закрытых свечей, на которое сдвигается сигнал ADX.
StartHour
Час начала торгового окна (UTC).
EndHour
Час окончания торгового окна (UTC).
CandleType
Тип свечей для расчётов.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Proffessor V3 strategy. Uses RSI 50-line crossover (period 10).
/// </summary>
public class ProffessorV3Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private decimal? _prevRsi;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public ProffessorV3Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 10).SetGreaterThanZero().SetDisplay("RSI Period", "RSI lookback", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = null;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevRsi = rsiVal; return; }
if (_prevRsi == null) { _prevRsi = rsiVal; return; }
if (_prevRsi.Value < 45m && rsiVal >= 55m && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (_prevRsi.Value > 55m && rsiVal <= 45m && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
_prevRsi = rsiVal;
}
}
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 proffessor_v3_strategy(Strategy):
def __init__(self):
super(proffessor_v3_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._rsi_period = self.Param("RsiPeriod", 10) \
.SetDisplay("RSI Period", "RSI lookback", "Indicators")
self._prev_rsi = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
def OnReseted(self):
super(proffessor_v3_strategy, self).OnReseted()
self._prev_rsi = None
def OnStarted2(self, time):
super(proffessor_v3_strategy, self).OnStarted2(time)
self._prev_rsi = None
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
if self._prev_rsi is None:
self._prev_rsi = rv
return
if self._prev_rsi < 45.0 and rv >= 55.0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_rsi > 55.0 and rv <= 45.0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_rsi = rv
def CreateClone(self):
return proffessor_v3_strategy()