在 GitHub 上查看
Hans123Trader 策略(StockSharp 实现)
策略简介
Hans123Trader 策略使用 StockSharp 高级 API 重构了 MetaTrader 专家顾问 “Hans123Trader v1”。系统每天在两个固定时段预挂突破方向的止损单,依据最近 80 根 5 分钟 K 线形成的价格区间。该方案主要面向外汇品种,默认假设 PriceStep 对应于小数点后最小报价单位。每天开盘前都会刷新挂单,并在跨日时强制平掉未结的仓位。
工作流程
- 区间跟踪:通过
Highest 与 Lowest 指标维护 80 根五分钟 K 线的滚动窗口,得到最近的最高价与最低价。
- 时段控制:参数
EndSession1、EndSession2 定义两个交易窗口。当时间到达设定的整点(分钟为 00)时,策略重新计算并提交新的止损挂单。
- 挂单逻辑:买入止损设置在最高价上方
5 个点,卖出止损设置在最低价下方 5 个点。新的一天开始时自动撤销上一日残留的挂单,以模拟 MetaTrader 23:59 的到期机制。
- 仓位管理:成交后按照配置应用初始止损、可选的止盈以及拖尾止损。所有距离参数都以点数表示,并根据
PriceStep 转换成实际价格差。
- 日切清理:若持仓跨入下一交易日,会立即以市价平仓。同时清空上一日的挂单引用后再准备新的挂单。
交易规则
- 入场条件
- 每天最多两次尝试:分别在
EndSession1 与 EndSession2 指定的小时触发。
- 买入止损价 =
最高价 + 5 点;卖出止损价 = 最低价 − 5 点。
- 挂单数量取自策略参数
Volume(默认 1)。
- 若计算后的下单量 ≤ 0,则跳过挂单。
- 出场规则
- 初始止损 = 入场价 ±
InitialStopLoss 点(多头减,空头加)。
- 止盈 = 入场价 ±
TakeProfit 点(多头加,空头减)。
- 拖尾止损在收盘价朝盈利方向前进至少
TrailingStop 点时,提高保护价位。
- 任何跨日仓位都会立即市价平仓。
- 挂单维护
- 每个自然日开始时撤销所有挂单。
- 挂单被触发、取消或失败后,相应引用会被清除。
参数说明
| 参数 |
说明 |
BeginSession1 / BeginSession2 |
为保持界面兼容而保留的起始时刻提示,目前逻辑依赖结束时间触发。 |
EndSession1 / EndSession2 |
触发挂单的小时数(0–23),分钟必须为 0。 |
TrailingStop |
拖尾止损距离(点)。为 0 表示关闭。 |
TakeProfit |
止盈距离(点)。为 0 表示关闭。 |
InitialStopLoss |
初始止损距离(点)。为 0 则无固定止损(除非拖尾启动)。 |
CandleType |
用于构建 80 根区间的 K 线类型(默认 5 分钟)。 |
Volume |
策略下单量。 |
转换细节
- MetaTrader 中的
OrderSendExtended 以及全局变量锁不再需要,StockSharp 自身负责并发控制。
- 原有的 magic number 由
_session* 字段替代,通过订单事件自动清理引用。
- 挂单 23:59 自动失效改为“跨日即撤单”。
- 拖尾止损使用 K 线收盘价近似 MetaTrader 的 Bid/Ask。
- 点数参数会乘以
Security.PriceStep 转换成价格差;若未设置 PriceStep,则按绝对价差处理。
使用建议
- 请选择已正确配置
PriceStep、StepPrice、VolumeStep 的品种,确保点值与下单量换算准确。
- 确保具备足够的 5 分钟历史数据,80 根窗口是信号基础。
- 根据交易习惯调整
EndSession1/EndSession2,例如对应伦敦和纽约开盘前的波动。
- 在真实交易前,利用 Designer 或 Runner 优化
InitialStopLoss、TakeProfit、TrailingStop 参数。
- 若多个策略共用组合,建议结合 StockSharp 的额外风控模块。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Hans123 breakout strategy. Builds a range from recent highest/lowest prices
/// and enters on breakout above range high or below range low.
/// </summary>
public class Hans123TraderRangeBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _rangeLength;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal _prevHighest;
private decimal _prevLowest;
public Hans123TraderRangeBreakoutStrategy()
{
_rangeLength = Param(nameof(RangeLength), 20)
.SetDisplay("Range Length", "Number of candles used to compute the breakout range.", "Breakout");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle series for range detection.", "General");
}
public int RangeLength
{
get => _rangeLength.Value;
set => _rangeLength.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_prevHighest = 0;
_prevLowest = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_prevHighest = 0;
_prevLowest = 0;
var highest = new Highest { Length = RangeLength };
var lowest = new Lowest { Length = RangeLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, highest);
DrawIndicator(area, lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
if (highestValue <= 0 || lowestValue <= 0)
{
_prevHighest = highestValue;
_prevLowest = lowestValue;
return;
}
// Entry on breakout using previous levels
if (Position == 0 && _prevHighest > 0 && _prevLowest > 0)
{
if (candle.ClosePrice > _prevHighest)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (candle.ClosePrice < _prevLowest)
{
SellMarket();
_entryPrice = candle.ClosePrice;
}
}
_prevHighest = highestValue;
_prevLowest = lowestValue;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import Highest, Lowest
class hans123_trader_range_breakout_strategy(Strategy):
def __init__(self):
super(hans123_trader_range_breakout_strategy, self).__init__()
self._range_length = self.Param("RangeLength", 20) \
.SetDisplay("Range Length", "Number of candles used to compute the breakout range", "Breakout")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle series for range detection", "General")
self._entry_price = 0.0
self._stop_price = 0.0
self._prev_highest = 0.0
self._prev_lowest = 0.0
@property
def RangeLength(self):
return self._range_length.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(hans123_trader_range_breakout_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._stop_price = 0.0
self._prev_highest = 0.0
self._prev_lowest = 0.0
self._highest = Highest()
self._highest.Length = self.RangeLength
self._lowest = Lowest()
self._lowest.Length = self.RangeLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._highest, self._lowest, self.ProcessCandle).Start()
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(1, UnitTypes.Percent)
)
def ProcessCandle(self, candle, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
hv = float(highest_value)
lv = float(lowest_value)
if hv <= 0 or lv <= 0:
self._prev_highest = hv
self._prev_lowest = lv
return
# Entry on breakout using previous levels
if self.Position == 0 and self._prev_highest > 0 and self._prev_lowest > 0:
close = float(candle.ClosePrice)
if close > self._prev_highest:
self.BuyMarket()
self._entry_price = close
elif close < self._prev_lowest:
self.SellMarket()
self._entry_price = close
self._prev_highest = hv
self._prev_lowest = lv
def OnReseted(self):
super(hans123_trader_range_breakout_strategy, self).OnReseted()
self._entry_price = 0.0
self._stop_price = 0.0
self._prev_highest = 0.0
self._prev_lowest = 0.0
def CreateClone(self):
return hans123_trader_range_breakout_strategy()