Стратегия представляет собой порт советника MetaTrader 4 «Huge Income» на платформу StockSharp. Исходный робот ищет внутридневные движения, которые значительно удаляются от дневного открытия, и открывает одну позицию в сторону прорыва. Версия для StockSharp воспроизводит эту логику: восстанавливает дневной максимум и минимум по внутридневным свечам, допускает только одну позицию одновременно и принудительно закрывает её незадолго до заданного времени окончания сессии.
Данные и окружение
Инструменты: любой инструмент с корректно заданным шагом цены (PriceStep). Алгоритм изначально написан для валютных пар, но после настройки параметров пипсов может применяться и к другим рынкам.
Таймфрейм: по умолчанию подписка идёт на 15-минутные свечи, чтобы восстановить дневное открытие, максимум и минимум. При необходимости можно выбрать другой тип свечей, если источник данных предоставляет более высокое разрешение.
Сессии: ожидается, что время свечей совпадает со временем торгового сервера, как и в MetaTrader. Настраивайте граничные часы в соответствии с этой временной зоной.
Логика торговли
При появлении новой свечи пересчитываются показатели текущего дня. Первая свеча дня задаёт цену открытия и инициализирует экстремумы.
В любой момент может существовать только одна позиция (длинная или короткая). Отложенные заявки не используются — стратегия опирается на рыночные приказы.
Условия для покупки:
Текущая цена закрытия выше дневного открытия.
Расстояние между открытием и дневным минимумом превышает MinimumRangePips (переведённое в абсолютную цену через PriceStep).
Текущий час строго меньше BuyCutoffHour.
Условия для продажи:
Текущая цена закрытия ниже дневного открытия.
Расстояние между дневным максимумом и открытием превышает MinimumRangePips.
Текущий час строго меньше SellCutoffHour.
При выполнении условий отправляется рыночный ордер объёмом TradeVolume, после чего стратегия не рассматривает новые входы до полного закрытия позиции.
После наступления MarketCloseHour любая открытая позиция закрывается рыночным приказом. Это повторяет цикл OrderClose из оригинального кода, предназначенный для закрытия перед завершением торговой недели.
Управление рисками и капиталом
TradeVolume — фиксированный объём сделки. В оригинальном советнике нет усреднения или мартингейла, поэтому порт также использует постоянный объём.
Отдельных стоп-лоссов и тейк-профитов не предусмотрено. Контроль риска обеспечивается фильтром дневного диапазона и принудительным закрытием позиции перед окончанием сессии. При необходимости стратегию можно расширить стоп-заявками или трейлингом.
Параметры
Параметр
Описание
TradeVolume
Объём позиции для рыночных приказов BuyMarket и SellMarket.
MinimumRangePips
Минимальное расстояние (в пипсах) между дневным открытием и противоположным экстремумом, необходимое для входа. Конвертируется в абсолютную цену через Security.PriceStep.
BuyCutoffHour
Последний час (0–23), когда допускаются новые покупки. Сравнение строгое (currentHour < BuyCutoffHour).
SellCutoffHour
Последний час (0–23), когда допускаются новые продажи.
MarketCloseHour
Час, в который все открытые позиции принудительно закрываются. Значение 23 соответствует поведению исходного советника по пятницам.
CandleType
Тип свечей, используемых для подписки и восстановления дневной статистики.
Отличия от версии MT4
StockSharp получает свечные данные вместо тиков. Если в MetaTrader стратегия работала по тикам, выбирайте достаточно мелкий таймфрейм свечей, чтобы сохранить скорость реакции.
Фильтр MinimumRangePips автоматически отключается, если у инструмента отсутствует PriceStep. В этом случае входы разрешены при любом прорыве открытия.
Все сделки исполняются рыночными приказами и немедленно закрываются в момент MarketCloseHour, что воспроизводит цикл OrderClose без использования отложенных ордеров.
Рекомендации по использованию
Подберите таймфрейм свечей под желаемую скорость исполнения. Более короткие свечи точнее отслеживают дневные экстремумы, но требуют большего объёма данных.
Уточните торговые часы инструмента. Если рынок закрывается раньше указанного MarketCloseHour, принудительный выход произойдёт на следующей торговой сессии.
При необходимости добавьте портфельные или аккаунтные ограничения (например, через StartProtection), чтобы расширить контроль риска по сравнению с оригинальной версией.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Huge Income: EMA trend following with ATR stops.
/// </summary>
public class HugeIncomeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevClose;
private decimal _entryPrice;
public HugeIncomeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 0; _entryPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (atrVal <= 0 || _prevClose == 0) { _prevClose = candle.ClosePrice; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if (close < emaVal || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close > emaVal || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (close > emaVal && _prevClose <= emaVal) { _entryPrice = close; BuyMarket(); }
else if (close < emaVal && _prevClose >= emaVal) { _entryPrice = close; SellMarket(); }
}
_prevClose = close;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class huge_income_strategy(Strategy):
def __init__(self):
super(huge_income_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_close = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(huge_income_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
av = float(atr_val)
close = float(candle.ClosePrice)
if av <= 0 or self._prev_close == 0:
self._prev_close = close
return
if self.Position > 0:
if close < ev or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close > ev or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > ev and self._prev_close <= ev:
self._entry_price = close
self.BuyMarket()
elif close < ev and self._prev_close >= ev:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(huge_income_strategy, self).OnReseted()
self._prev_close = 0.0
self._entry_price = 0.0
def CreateClone(self):
return huge_income_strategy()