Стратегия SuperForexV2
Обзор
SuperForexV2 — это перенос советника MetaTrader 4 SuperForexV2.mq4 на платформу StockSharp. Исходный робот торгует по краткосро-
чному индикатору RSI и использует фиксированные расстояния до тейк-профита, стоп-лосса и трейлинг-стопа. Реализация на C# повторяет
эти решения через высокоуровневый API StockSharp: стратегия анализирует только закрывшиеся свечи, реагирует на пересечения порогов
RSI и управляет единственной позицией с помощью ценовых уровней, выраженных в пунктах.
Логика торговли
- Поток данных и индикатор
- Подписывается на настраиваемый поток свечей (по умолчанию 15-минутные бары) и передаёт каждую закрытую свечу в индикатор RSI.
- Период RSI задаётся параметром и равен 4, как в оригинале.
- Динамический объём сделки
- Перед входом рассчитывается рабочий объём как отношение текущей стоимости портфеля к
BalanceToVolumeDivider.
- Полученное значение ограничивается параметрами
InitialVolume и MaxVolume, после чего приводится к шагу объёма инструмента.
- Условия входа
- Если позиции нет и RSI опускается ниже
RsiLowerLevel, отправляется рыночная покупка.
- Если RSI поднимается выше
RsiUpperLevel, открывается короткая позиция.
- Управление позицией
- При открытии сделки фиксируются абсолютные уровни стоп-лосса и тейк-профита, рассчитанные из пунктов.
- На каждой закрытой свече проверяется, достигались ли эти уровни; при срабатывании позиция закрывается по рынку.
- Трейлинг-стоп переносит уровень стопа после движения цены в прибыльную сторону минимум на
TrailingStopPips пунктов.
- Дополнительно позиция закрывается при достижении противоположного порога RSI (например, лонг ликвидируется при RSI выше верхней
границы).
- Контроль количества сделок
- Подобно MT4-версии, стратегия поддерживает не более одной чистой позиции: пока есть активная сделка, новые входы не рассматри-
ваются.
Параметры
| Название |
Описание |
Значение по умолчанию |
Примечания |
CandleType |
Тип свечей для расчётов. |
Таймфрейм 15m |
Можно выбрать любой DataType, поддерживаемый коннектором. |
RsiPeriod |
Длина RSI. |
4 |
Значение должно быть больше нуля. |
RsiUpperLevel |
Верхний порог RSI для шортов и выхода из лонгов. |
62 |
Соответствует параметру Pos в MT4. |
RsiLowerLevel |
Нижний порог RSI для лонгов и выхода из шортов. |
42 |
Соответствует параметру Neg в MT4. |
TakeProfitPips |
Расстояние до тейк-профита в пунктах. |
109 |
При 0 тейк-профит отключается. |
StopLossPips |
Расстояние до стоп-лосса в пунктах. |
9 |
При 0 стоп-лосс отключается. |
TrailingStopPips |
Длина трейлинг-стопа в пунктах. |
6 |
При 0 трейлинг не используется. |
InitialVolume |
Минимальный объём при отсутствии данных о портфеле. |
0.1 |
Используется и тогда, когда расчётный объём ≤ 0. |
MaxVolume |
Максимальный допустимый объём входа. |
100 |
Ограничивает рост позиции при большом балансе. |
BalanceToVolumeDivider |
Делитель баланса при расчёте объёма. |
10000 |
Повторяет формулу MT4 Lots = AccountBalance()/10000. |
Особенности реализации
- Анализ выполняется только после закрытия свечи (
CandleStates.Finished), что эквивалентно обработке тиков MT4 по окончании бара и
одновременно защищает от неполных данных.
- Перевод пунктов в цену основан на
PriceStep инструмента. Для трёх- и пятизнаковых валютных пар шаг дополнительно умножается на 10,
чтобы привести понятие пункта к метатрейдеровскому.
- Уровни стоп-лосса, тейк-профита и трейлинга хранятся в самой стратегии и сравниваются с экстремумами свечей, поскольку высокоуровне-
вые ордера StockSharp не управляют биржевыми стопами автоматически.
- Рассчитанный объём корректируется по
VolumeStep, MinVolume и MaxVolume, чтобы заявка соответствовала торговым ограничениям.
- При наличии позиции метод входа немедленно завершается, гарантируюя отсутствие параллельных сделок.
Отличия от версии для MT4
- StockSharp-реализация работает по закрытым свечам, поэтому факты достижения стопов/тейков внутри бара фиксируются на следующей свече.
- Проверка
AccountFreeMargin() заменена вычислением объёма через стоимость портфеля; если данные недоступны, используется InitialVolume.
- Стратегия не выставляет стоп-ордеры брокеру, а закрывает позиции рыночными приказами при достижении уровня — это соответствует
возможностям высокоуровневого API StockSharp.
- Параметр
NumeroMagico, применявшийся для фильтрации сделок в MT4, не требуется и опущен.
- Текстовые сообщения
Print из оригинального советника не перенесены; при необходимости используйте систему логирования StockSharp.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// SuperForexV2: RSI threshold reversal with ATR trailing stop.
/// Buys when RSI below lower level, sells when above upper level.
/// </summary>
public class SuperForexV2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _upperLevel;
private readonly StrategyParam<decimal> _lowerLevel;
private decimal _entryPrice;
private decimal _trailStop;
public SuperForexV2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 4)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for trailing.", "Indicators");
_upperLevel = Param(nameof(UpperLevel), 62m)
.SetDisplay("RSI Upper", "Overbought for shorts.", "Signals");
_lowerLevel = Param(nameof(LowerLevel), 42m)
.SetDisplay("RSI Lower", "Oversold for longs.", "Signals");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal UpperLevel
{
get => _upperLevel.Value;
set => _upperLevel.Value = value;
}
public decimal LowerLevel
{
get => _lowerLevel.Value;
set => _lowerLevel.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_trailStop = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_trailStop = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
// Trailing stop + opposite RSI exit
if (Position > 0)
{
var newTrail = close - atrVal * 1.5m;
if (newTrail > _trailStop)
_trailStop = newTrail;
if (close <= _trailStop || rsiVal > UpperLevel)
{
SellMarket();
_entryPrice = 0;
_trailStop = 0;
}
}
else if (Position < 0)
{
var newTrail = close + atrVal * 1.5m;
if (_trailStop == 0 || newTrail < _trailStop)
_trailStop = newTrail;
if (close >= _trailStop || rsiVal < LowerLevel)
{
BuyMarket();
_entryPrice = 0;
_trailStop = 0;
}
}
// Entry on RSI levels
if (Position == 0)
{
if (rsiVal < LowerLevel)
{
_entryPrice = close;
_trailStop = close - atrVal * 2m;
BuyMarket();
}
else if (rsiVal > UpperLevel)
{
_entryPrice = close;
_trailStop = close + atrVal * 2m;
SellMarket();
}
}
}
}
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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class super_forex_v2_strategy(Strategy):
"""RSI threshold reversal with ATR trailing stop."""
def __init__(self):
super(super_forex_v2_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 4).SetDisplay("RSI Length", "RSI period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period for trailing", "Indicators")
self._upper_level = self.Param("UpperLevel", 62.0).SetDisplay("RSI Upper", "Overbought for shorts", "Signals")
self._lower_level = self.Param("LowerLevel", 42.0).SetDisplay("RSI Lower", "Oversold for longs", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(super_forex_v2_strategy, self).OnReseted()
self._entry_price = 0
self._trail_stop = 0
def OnStarted2(self, time):
super(super_forex_v2_strategy, self).OnStarted2(time)
self._entry_price = 0
self._trail_stop = 0
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(rsi, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
if atr_val <= 0:
return
close = float(candle.ClosePrice)
rsi_val = float(rsi_val)
atr_val = float(atr_val)
upper = self._upper_level.Value
lower = self._lower_level.Value
# Trailing stop + opposite RSI exit
if self.Position > 0:
new_trail = close - atr_val * 1.5
if new_trail > self._trail_stop:
self._trail_stop = new_trail
if close <= self._trail_stop or rsi_val > upper:
self.SellMarket()
self._entry_price = 0
self._trail_stop = 0
elif self.Position < 0:
new_trail = close + atr_val * 1.5
if self._trail_stop == 0 or new_trail < self._trail_stop:
self._trail_stop = new_trail
if close >= self._trail_stop or rsi_val < lower:
self.BuyMarket()
self._entry_price = 0
self._trail_stop = 0
# Entry on RSI levels
if self.Position == 0:
if rsi_val < lower:
self._entry_price = close
self._trail_stop = close - atr_val * 2
self.BuyMarket()
elif rsi_val > upper:
self._entry_price = close
self._trail_stop = close + atr_val * 2
self.SellMarket()
def CreateClone(self):
return super_forex_v2_strategy()