在 GitHub 上查看
Hard Profit
概述
Hard Profit 是对 MetaTrader 4 专家顾问 hardprofit.mq4 的 StockSharp 移植版本。策略试图在价格收于蜡烛极值且平滑
趋势滤波确认方向时捕捉突破。移植过程中使用 StockSharp 的高级 API 重建了原始 EA 的资金管理、分批止盈以及止损
管理逻辑。
策略逻辑
突破结构
- 监控所选时间框架的收盘蜡烛,维护过去
Breakout Period 根蜡烛(不含当前蜡烛)的最高价与最低价,复现
iHighest/iLowest 带偏移的行为。
- 使用中价驱动长度为
Trend Period 的平滑移动平均,其当前值与上一值的差值充当方向过滤器。
入场规则
- 做多入场 需要满足:
- 蜡烛收盘价等于最高价且向上突破前高区间;
- 平滑均线斜率为正;
- 当前无持仓且未达到每根蜡烛的入场次数限制;
- 同时存在买一卖一报价且点差小于
Max Spread (pips);
- 未开启
Only Short(仅做空)限制。
- 做空入场 完全对称:收于最低价并跌破前低区间、均线斜率为负、满足点差过滤,且
Only Long 未启用。
离场与风控
- 固定止损(
Stop Loss (pips))与可选止盈(Take Profit (pips))形成外部保护区间。
- 浮动盈利达到
Break-even (pips) 时,止损上移至开仓价;盈利达到 Trailing Activation (pips) 时,止损再向前
推进一个止损距离,锁定收益。
- 两个分批止盈沿用原始 EA 的比例:
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 – 基于 Proportional 结果,如果出现超过一次连续亏损,则按
Decrease Factor 减小下次仓位。
- TSSF – 复刻 Triggered Smart Safe-Factor 逻辑,从最近
Last Trades 笔已实现收益中计算平均盈利、平均亏损
与胜率,根据 TSSF Trigger 与 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 – 驱动指标与信号的主时间框架。
补充说明
- Pip 大小由证券的价格步长推导,五位或三位外汇品种会自动映射为 10 个最小报价点。
- 分批止盈不会重置每根蜡烛的入场计数,保持与原始 EA 相同的处理方式。
- 资金管理统计基于已实现盈亏的增量计算,因此在 StockSharp 环境下完成第一笔平仓后才会逐渐生效。
- 当缺少买一卖一报价时,点差过滤自动失效,与原始 EA 在经纪商返回零点差时的行为一致。
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()