在 GitHub 上查看

Twenty 200 Pips 策略

概述

该策略复刻了 MQL5 顾问 20/200 pips 的思想。它分析小时K线,并比较两个历史开盘价 (Open[t1]Open[t2])。当在指定小时内这两个开盘价之间的差值超过阈值时,策略只 会在当天开立一笔仓位,同时依赖固定的止盈和止损。

交易逻辑

  1. 订阅所需周期的K线(默认 1 小时),并将开盘价输入两个 Shift 指标,用于直接获取 对应索引处的开盘价,而无需维护自定义数组。
  2. 每根完结的K线都会检查当前小时是否已经大于设置的 TradeHour,如果是则重新允许 下一笔交易,从而复制原始顾问的每日重置逻辑。
  3. 当当前小时与 TradeHour 相等、没有持仓且允许交易时,比较保存的开盘价:
    • 如果 Open[t1] > Open[t2] + delta,提交市价卖单。
    • 如果 Open[t1] + delta < Open[t2],提交市价买单。
  4. 发送订单后会立即禁止新的入场,直到下一次时间窗口再次打开。StartProtection 负责自动挂出止盈和止损。

参数

  • TakeProfit – 止盈距离(以价格点数表示,默认 200 点)。
  • StopLoss – 止损距离(以价格点数表示,默认 2000 点)。
  • TradeHour – 检查信号的小时(0-23,默认 18)。
  • FirstOffset – 较旧的开盘价索引(对应 MQL 中的 Open[t1],默认 7)。
  • SecondOffset – 较新的开盘价索引(对应 Open[t2],默认 2)。
  • DeltaPoints – 触发交易所需的最小开盘价差(默认 70 点)。
  • Volume – 市价单的下单数量(默认 0.1)。
  • CandleType – 计算时使用的K线类型(默认 1 小时)。

实现细节

  • 通过手动调用 Shift 指标来读取历史开盘价,避免创建额外的数据集合。
  • OnStarted 中调用一次 StartProtection,以模拟原始顾问中为每笔交易设置的止盈 和止损。
  • 代码中添加了英文注释,方便审阅和后续维护。
  • 每天只允许一次入场:下单后 _canTrade 立即被清零,只有在当前小时再次超过 TradeHour 时才会恢复。

使用方式

  1. 将策略附加到目标证券上,并根据交易品种调整参数。
  2. 确保证券定义了有效的 PriceStep,因为点数参数会依据该值转换成绝对价格距离。
  3. 启动策略后,它会等待设定的小时,并在下一根完成的K线上检查开盘价差异以决定是否 入场。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Daily breakout strategy derived from the "20/200 pips" MQL5 expert.
/// Compares open prices from different bar offsets and trades the breakout.
/// </summary>
public class Twenty200PipsStrategy : Strategy
{
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;
	private readonly StrategyParam<int> _firstOffset;
	private readonly StrategyParam<int> _secondOffset;
	private readonly StrategyParam<int> _deltaPoints;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _opens = new();
	private decimal _pointValue;

	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	public int FirstOffset
	{
		get => _firstOffset.Value;
		set => _firstOffset.Value = value;
	}

	public int SecondOffset
	{
		get => _secondOffset.Value;
		set => _secondOffset.Value = value;
	}

	public int DeltaPoints
	{
		get => _deltaPoints.Value;
		set => _deltaPoints.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public Twenty200PipsStrategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 200)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (points)", "Take profit distance in points", "Risk")
			.SetOptimize(50, 500, 50);

		_stopLoss = Param(nameof(StopLoss), 2000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (points)", "Stop loss distance in points", "Risk")
			.SetOptimize(200, 4000, 100);

		_firstOffset = Param(nameof(FirstOffset), 7)
			.SetGreaterThanZero()
			.SetDisplay("First Offset", "Older bar index", "Signal")
			.SetOptimize(1, 12, 1);

		_secondOffset = Param(nameof(SecondOffset), 2)
			.SetGreaterThanZero()
			.SetDisplay("Second Offset", "Newer bar index", "Signal")
			.SetOptimize(1, 6, 1);

		_deltaPoints = Param(nameof(DeltaPoints), 1)
			.SetGreaterThanZero()
			.SetDisplay("Delta (points)", "Minimum difference between opens", "Signal")
			.SetOptimize(10, 200, 10);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_opens.Clear();
		_pointValue = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_pointValue = Security?.PriceStep ?? 1m;
		if (_pointValue <= 0m)
			_pointValue = 1m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}

		StartProtection(
			takeProfit: new Unit(TakeProfit * _pointValue, UnitTypes.Absolute),
			stopLoss: new Unit(StopLoss * _pointValue, UnitTypes.Absolute),
			useMarketOrders: true);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_opens.Add(candle.OpenPrice);

		var maxOffset = Math.Max(FirstOffset, SecondOffset);
		if (_opens.Count <= maxOffset)
			return;

		// Keep buffer limited
		if (_opens.Count > maxOffset + 100)
			_opens.RemoveRange(0, _opens.Count - maxOffset - 50);

		if (Position != 0)
			return;

		var openFirst = _opens[_opens.Count - 1 - FirstOffset];
		var openSecond = _opens[_opens.Count - 1 - SecondOffset];
		var threshold = DeltaPoints * _pointValue;

		if (openFirst > openSecond + threshold)
		{
			SellMarket();
		}
		else if (openFirst + threshold < openSecond)
		{
			BuyMarket();
		}
	}
}