Стратегия представляет собой перенос эксперта MetaTrader «HTH Trader». Она торгует корзину из четырёх валютных пар, пытаясь использовать дневное расхождение между EURUSD и зеркальной комбинацией USDCHF, GBPUSD и AUDUSD. Реализация для StockSharp сохраняет первоначальные правила по времени, управлению риском и экстренному усреднению.
Основные особенности:
Один раз в сутки, в промежутке 00:05–00:12 по серверному времени, формируется хедж из четырёх позиций.
Направление корзины определяется знаком изменения закрытия EURUSD за предыдущие два торговых дня.
Одновременно управляет четырьмя инструментами: EURUSD (основной), USDCHF, GBPUSD и AUDUSD.
Подсчитывает общий нереализованный результат в пунктах и поддерживает дневные цели по прибыли и убытку.
Содержит аварийную функцию удвоения прибыльных ног при достижении заданной просадки.
Закрывает все позиции в 23:00 или при срабатывании целевых значений.
Требования к данным
Внутридневные свечи: все четыре инструмента должны предоставлять свечи выбранного таймфрейма (IntradayCandleType, по умолчанию 5 минут) для контроля времени и текущей цены.
Дневные свечи: для каждого инструмента необходимы дневные свечи, чтобы отслеживать два последних закрытия.
Логика работы
После формирования каждой внутридневной свечи стратегия вычисляет суммарную прибыль в пунктах:
При включённом AllowEmergencyTrading и просадке ≤ -EmergencyLossPips происходит удвоение всех прибыльных ног, после чего аварийная функция отключается до следующего дня.
При активном UseProfitTarget и прибыли ≥ ProfitTargetPips корзина закрывается.
При активном UseLossLimit и убытке ≤ -LossLimitPips корзина закрывается.
В 23:00 все позиции закрываются независимо от результата.
Когда позиций нет и текущее время попадает в окно 00:05–00:12, анализируются два последних дневных закрытия EURUSD:
Положительное изменение запускает покупки EURUSD, USDCHF и AUDUSD и продажу GBPUSD.
Отрицательное изменение приводит к продажам EURUSD, USDCHF и AUDUSD и покупке GBPUSD.
При нулевом изменении или отсутствии данных торговля пропускается.
Закрытие осуществляется рыночными заявками через ClosePosition.
Параметры
Имя
Назначение
Значение по умолчанию
TradeEnabled
Разрешение на выставление сделок.
true
ShowProfitInfo
Логирование текущей прибыли корзины в пунктах.
true
UseProfitTarget
Автоматическое закрытие по цели прибыли.
false
UseLossLimit
Автоматическое закрытие по пределу убытка.
false
AllowEmergencyTrading
Разрешение аварийного удвоения.
true
EmergencyLossPips
Просадка в пунктах для запуска удвоения.
60
ProfitTargetPips
Цель по прибыли (пункты).
80
LossLimitPips
Допустимый убыток (пункты).
40
TradingVolume
Объём каждой ноги.
0.01
Symbol2
Второй инструмент (USDCHF по умолчанию).
null
Symbol3
Третий инструмент (GBPUSD по умолчанию).
null
Symbol4
Четвёртый инструмент (AUDUSD по умолчанию).
null
IntradayCandleType
Таймфрейм внутренних свечей.
Свечи 5 минут
Рекомендации по использованию
Назначьте Strategy.Security на EURUSD (или другой ведущий актив) и заполните параметры Symbol2, Symbol3, Symbol4 соответствующими бумагами до запуска.
Убедитесь, что для каждого инструмента задан корректный PriceStep; без него расчёт пунктов невозможен и аварийная логика не сработает.
Аварийное удвоение применяется только к прибыльным ногам, чтобы не увеличивать текущий убыток.
Предполагается исполнение рыночных заявок около цены закрытия последней свечи; для точного повторения необходимо обеспечить стабильный поток внутридневных данных.
Поскольку стратегия реагирует на закрытие свечей, возможны небольшие отличия по времени входа относительно тиковой версии в MetaTrader, однако последовательность решений полностью соответствует оригиналу.
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>
/// Hedge strategy simplified from HTH Trader. Trades based on daily close deviation.
/// </summary>
public class HthTraderStrategy : Strategy
{
private readonly StrategyParam<bool> _tradeEnabled;
private readonly StrategyParam<bool> _useProfitTarget;
private readonly StrategyParam<bool> _useLossLimit;
private readonly StrategyParam<int> _profitTargetPips;
private readonly StrategyParam<int> _lossLimitPips;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose1;
private decimal _prevClose2;
private decimal _entryPrice;
private decimal _priceStep;
/// <summary>
/// Enable automated trading.
/// </summary>
public bool TradeEnabled
{
get => _tradeEnabled.Value;
set => _tradeEnabled.Value = value;
}
/// <summary>
/// Enable closing by reaching the profit target.
/// </summary>
public bool UseProfitTarget
{
get => _useProfitTarget.Value;
set => _useProfitTarget.Value = value;
}
/// <summary>
/// Enable closing by reaching the loss limit.
/// </summary>
public bool UseLossLimit
{
get => _useLossLimit.Value;
set => _useLossLimit.Value = value;
}
/// <summary>
/// Profit target in pips.
/// </summary>
public int ProfitTargetPips
{
get => _profitTargetPips.Value;
set => _profitTargetPips.Value = value;
}
/// <summary>
/// Loss limit in pips.
/// </summary>
public int LossLimitPips
{
get => _lossLimitPips.Value;
set => _lossLimitPips.Value = value;
}
/// <summary>
/// Candle type for monitoring.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes parameters.
/// </summary>
public HthTraderStrategy()
{
_tradeEnabled = Param(nameof(TradeEnabled), true)
.SetDisplay("Trade Enabled", "Allow the strategy to submit orders", "General");
_useProfitTarget = Param(nameof(UseProfitTarget), true)
.SetDisplay("Use Profit Target", "Close when profit target is reached", "Risk");
_useLossLimit = Param(nameof(UseLossLimit), true)
.SetDisplay("Use Loss Limit", "Close when loss limit is reached", "Risk");
_profitTargetPips = Param(nameof(ProfitTargetPips), 80)
.SetDisplay("Profit Target (pips)", "Profit target in pips", "Risk");
_lossLimitPips = Param(nameof(LossLimitPips), 40)
.SetDisplay("Loss Limit (pips)", "Loss limit in pips", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for monitoring", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose1 = 0m;
_prevClose2 = 0m;
_entryPrice = 0m;
_priceStep = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 0.0001m;
if (_priceStep <= 0m)
_priceStep = 0.0001m;
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!TradeEnabled)
return;
// Check exit conditions
if (Position != 0 && _entryPrice > 0m)
{
var priceDiff = Position > 0
? candle.ClosePrice - _entryPrice
: _entryPrice - candle.ClosePrice;
var pipsDiff = priceDiff / _priceStep;
if (UseProfitTarget && pipsDiff >= ProfitTargetPips)
{
if (Position > 0) SellMarket();
else BuyMarket();
_entryPrice = 0m;
return;
}
if (UseLossLimit && pipsDiff <= -LossLimitPips)
{
if (Position > 0) SellMarket();
else BuyMarket();
_entryPrice = 0m;
return;
}
}
// Entry logic based on daily close deviation
if (Position == 0 && _prevClose1 > 0m && _prevClose2 > 0m)
{
var deviation = (100m * _prevClose1 / _prevClose2) - 100m;
if (deviation > 0.1m)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (deviation < -0.1m)
{
SellMarket();
_entryPrice = candle.ClosePrice;
}
}
_prevClose2 = _prevClose1;
_prevClose1 = candle.ClosePrice;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class hth_trader_strategy(Strategy):
def __init__(self):
super(hth_trader_strategy, self).__init__()
self._trade_enabled = self.Param("TradeEnabled", True)
self._use_profit_target = self.Param("UseProfitTarget", True)
self._use_loss_limit = self.Param("UseLossLimit", True)
self._profit_target_pips = self.Param("ProfitTargetPips", 80)
self._loss_limit_pips = self.Param("LossLimitPips", 40)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_close1 = 0.0
self._prev_close2 = 0.0
self._entry_price = 0.0
self._price_step = 0.0
@property
def TradeEnabled(self):
return self._trade_enabled.Value
@TradeEnabled.setter
def TradeEnabled(self, value):
self._trade_enabled.Value = value
@property
def UseProfitTarget(self):
return self._use_profit_target.Value
@UseProfitTarget.setter
def UseProfitTarget(self, value):
self._use_profit_target.Value = value
@property
def UseLossLimit(self):
return self._use_loss_limit.Value
@UseLossLimit.setter
def UseLossLimit(self, value):
self._use_loss_limit.Value = value
@property
def ProfitTargetPips(self):
return self._profit_target_pips.Value
@ProfitTargetPips.setter
def ProfitTargetPips(self, value):
self._profit_target_pips.Value = value
@property
def LossLimitPips(self):
return self._loss_limit_pips.Value
@LossLimitPips.setter
def LossLimitPips(self, value):
self._loss_limit_pips.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(hth_trader_strategy, self).OnStarted2(time)
self._price_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
if self._price_step <= 0.0:
self._price_step = 0.0001
self._prev_close1 = 0.0
self._prev_close2 = 0.0
self._entry_price = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if not self.TradeEnabled:
return
close = float(candle.ClosePrice)
# Check exit conditions
if self.Position != 0 and self._entry_price > 0.0:
if self.Position > 0:
price_diff = close - self._entry_price
else:
price_diff = self._entry_price - close
pips_diff = price_diff / self._price_step
if self.UseProfitTarget and pips_diff >= int(self.ProfitTargetPips):
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._entry_price = 0.0
return
if self.UseLossLimit and pips_diff <= -int(self.LossLimitPips):
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._entry_price = 0.0
return
# Entry logic
if self.Position == 0 and self._prev_close1 > 0.0 and self._prev_close2 > 0.0:
deviation = (100.0 * self._prev_close1 / self._prev_close2) - 100.0
if deviation > 0.1:
self.BuyMarket()
self._entry_price = close
elif deviation < -0.1:
self.SellMarket()
self._entry_price = close
self._prev_close2 = self._prev_close1
self._prev_close1 = close
def OnReseted(self):
super(hth_trader_strategy, self).OnReseted()
self._prev_close1 = 0.0
self._prev_close2 = 0.0
self._entry_price = 0.0
self._price_step = 0.0
def CreateClone(self):
return hth_trader_strategy()