Hard Profit — это порт стратегии MetaTrader 4 hardprofit.mq4 на платформу StockSharp. Стратегия ищет импульсный пробой в моменты,
когда свеча закрывается на экстремуме, а сглаженный тренд подтверждает направление. При портировании воспроизведены режимы управления
капиталом, ступенчатый выход и защита позиции с использованием высокоуровневого API StockSharp.
Логика стратегии
Формирование пробоя
Стратегия обрабатывает закрытые свечи выбранного таймфрейма и отслеживает максимум и минимум предыдущих Breakout Period баров
(без текущей свечи), что полностью повторяет вызовы iHighest/iLowest с параметром сдвига 1 в исходном советнике.
Средняя цена (High+Low)/2 передается в сглаженную скользящую среднюю длиной Trend Period. Разница между текущим и прошлым
значением служит фильтром направления.
Правила входа
Длинная позиция открывается, если выполняются условия:
Свеча закрылась на максимуме и пробила предыдущий диапазон максимумов.
Наклон сглаженной средней положительный.
Позиция отсутствует, лимит Max Trades Per Bar не исчерпан.
При наличии котировок bid/ask текущий спред не превышает Max Spread (pips).
Не включен режим Only Short.
Короткая позиция открывается при зеркальных условиях: закрытие на минимуме, пробой предыдущего минимума, отрицательный наклон,
соблюдение фильтра по спреду и выключенный Only Long.
Управление позицией
Фиксированный стоп (Stop Loss (pips)) и опциональный тейк-профит (Take Profit (pips)) образуют внешний защитный диапазон.
При достижении прибыли Break-even (pips) стоп переносится на цену входа. После Trailing Activation (pips) стоп смещается дальше
на величину стоп-лосса, что полностью соответствует логике MetaTrader.
Реализованы два частичных выхода:
При достижении Partial TP1 (pips) закрывается Partial Ratio 1 (%) текущей позиции.
При достижении Partial TP2 (pips) закрывается Partial Ratio 2 (%) от оставшегося объема.
Расчёт основан на фактическом объеме позиции, поэтому второй выход автоматически учитывает первый.
Стопы и тейки реагируют на экстремумы внутри свечи: длинная позиция закрывается, если минимум пробивает стоп или максимум касается
цели; короткая позиция — при зеркальных условиях.
Управление капиталом
Реализованы пять режимов, адаптированных к данным портфеля StockSharp:
Fixed — каждый вход выполняется фиксированным объемом Fixed Volume.
Geometrical — размер сделки пропорционален квадратному корню из стоимости портфеля (0.1 * sqrt(balance / 1000) * Geometrical Factor).
Proportional — распределяет долю капитала относительно последней цены (equity * Risk Percent / (price * 1000)).
Smart — берет пропорциональный объем и уменьшает его при более чем одной убыточной сделке подряд, используя делитель Decrease Factor.
TSSF — повторяет Triggered Smart Safe-Factor. Средние прибыль/убыток и винрейт вычисляются по последним Last Trades закрытым
сделкам, после чего метрика переключает делители TSSF Ratio. При ухудшении условий объем возвращается к минимуму 0.1 лота.
Итоговый объем нормализуется по VolumeStep, MinVolume и MaxVolume инструмента.
Параметры
Breakout Period — количество свечей для расчета пробойных максимумов/минимумов.
Trend Period — период сглаженной скользящей средней.
Only Short / Only Long — флаги, ограничивающие торговлю одним направлением.
Max Trades Per Bar — ограничение на число входов в пределах одной свечи (0 — без лимита).
Stop Loss (pips) — расстояние защитного стопа, 0 отключает уровень.
Break-even (pips) — профит, при котором стоп переносится в безубыток.
Trailing Activation (pips) — профит, при котором стоп переносится дальше на размер исходного стопа.
Partial TP1 / Ratio 1 — дистанция и процент первого частичного выхода.
Partial TP2 / Ratio 2 — дистанция и процент второго частичного выхода.
Take Profit (pips) — финальная цель, 0 отключает жесткий тейк.
Max Spread (pips) — допустимый спред при открытии позиции.
Money Management — выбор режима управления капиталом (Fixed, Geometrical, Proportional, Smart, TSSF).
Fixed Volume — объем для режима Fixed.
Geometrical Factor — множитель в геометрической формуле.
Risk Percent — доля капитала для режимов Proportional, Smart и TSSF.
Last Trades — количество закрытых сделок, участвующих в адаптивной статистике.
Decrease Factor — делитель, уменьшающий объем при серии убыточных сделок в режиме Smart.
TSSF Trigger 1/2/3 и TSSF Ratio 1/2/3 — пороги и коэффициенты для режима TSSF.
Candle Type — таймфрейм, на котором рассчитываются индикаторы и сигналы.
Дополнительная информация
Размер пункта автоматически вычисляется из шага цены инструмента: для пятизначных валютных пар один пункт соответствует десяти шагах цены.
Частичные выходы не сбрасывают счетчик входов на свечу, что совпадает с поведением оригинального советника.
Статистика для режимов Smart и TSSF формируется по изменениям реализованной прибыли, поэтому она становится информативной после
первых закрытых сделок в среде StockSharp.
При отсутствии котировок bid/ask фильтр по спреду отключается, как и в исходном советнике при нулевом спреде брокера.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Hard Profit: Previous candle breakout with EMA filter and ATR stops.
/// </summary>
public class HardProfitStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _entryPrice;
public HardProfitStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).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();
_prevHigh = 0; _prevLow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0; _prevLow = 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;
var close = candle.ClosePrice;
if (_prevHigh == 0 || atrVal <= 0) { _prevHigh = candle.HighPrice; _prevLow = candle.LowPrice; return; }
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (close > _prevHigh && close > emaVal) { _entryPrice = close; BuyMarket(); }
else if (close < _prevLow && close < emaVal) { _entryPrice = close; SellMarket(); }
}
_prevHigh = candle.HighPrice; _prevLow = candle.LowPrice;
}
}
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 hard_profit_strategy(Strategy):
def __init__(self):
super(hard_profit_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.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_high = 0.0
self._prev_low = 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(hard_profit_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 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 self._prev_high == 0 or av <= 0:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.5 or close >= self._entry_price + av * 1.5:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
if self.Position == 0:
if close > self._prev_high and close > ev:
self._entry_price = close
self.BuyMarket()
elif close < self._prev_low and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def OnReseted(self):
super(hard_profit_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
def CreateClone(self):
return hard_profit_strategy()