在 GitHub 上查看

Hans123Trader 策略(StockSharp 实现)

策略简介

Hans123Trader 策略使用 StockSharp 高级 API 重构了 MetaTrader 专家顾问 “Hans123Trader v1”。系统每天在两个固定时段预挂突破方向的止损单,依据最近 80 根 5 分钟 K 线形成的价格区间。该方案主要面向外汇品种,默认假设 PriceStep 对应于小数点后最小报价单位。每天开盘前都会刷新挂单,并在跨日时强制平掉未结的仓位。

工作流程

  1. 区间跟踪:通过 HighestLowest 指标维护 80 根五分钟 K 线的滚动窗口,得到最近的最高价与最低价。
  2. 时段控制:参数 EndSession1EndSession2 定义两个交易窗口。当时间到达设定的整点(分钟为 00)时,策略重新计算并提交新的止损挂单。
  3. 挂单逻辑:买入止损设置在最高价上方 5 个点,卖出止损设置在最低价下方 5 个点。新的一天开始时自动撤销上一日残留的挂单,以模拟 MetaTrader 23:59 的到期机制。
  4. 仓位管理:成交后按照配置应用初始止损、可选的止盈以及拖尾止损。所有距离参数都以点数表示,并根据 PriceStep 转换成实际价格差。
  5. 日切清理:若持仓跨入下一交易日,会立即以市价平仓。同时清空上一日的挂单引用后再准备新的挂单。

交易规则

  • 入场条件
  • 每天最多两次尝试:分别在 EndSession1EndSession2 指定的小时触发。
  • 买入止损价 = 最高价 + 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,则按绝对价差处理。

使用建议

  • 请选择已正确配置 PriceStepStepPriceVolumeStep 的品种,确保点值与下单量换算准确。
  • 确保具备足够的 5 分钟历史数据,80 根窗口是信号基础。
  • 根据交易习惯调整 EndSession1/EndSession2,例如对应伦敦和纽约开盘前的波动。
  • 在真实交易前,利用 Designer 或 Runner 优化 InitialStopLossTakeProfitTrailingStop 参数。
  • 若多个策略共用组合,建议结合 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;
	}
}