Twenty 200 Pips 策略
概述
该策略复刻了 MQL5 顾问 20/200 pips 的思想。它分析小时K线,并比较两个历史开盘价
(Open[t1] 和 Open[t2])。当在指定小时内这两个开盘价之间的差值超过阈值时,策略只
会在当天开立一笔仓位,同时依赖固定的止盈和止损。
交易逻辑
- 订阅所需周期的K线(默认 1 小时),并将开盘价输入两个
Shift指标,用于直接获取 对应索引处的开盘价,而无需维护自定义数组。 - 每根完结的K线都会检查当前小时是否已经大于设置的
TradeHour,如果是则重新允许 下一笔交易,从而复制原始顾问的每日重置逻辑。 - 当当前小时与
TradeHour相等、没有持仓且允许交易时,比较保存的开盘价:- 如果
Open[t1] > Open[t2] + delta,提交市价卖单。 - 如果
Open[t1] + delta < Open[t2],提交市价买单。
- 如果
- 发送订单后会立即禁止新的入场,直到下一次时间窗口再次打开。
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时才会恢复。
使用方式
- 将策略附加到目标证券上,并根据交易品种调整参数。
- 确保证券定义了有效的
PriceStep,因为点数参数会依据该值转换成绝对价格距离。 - 启动策略后,它会等待设定的小时,并在下一根完成的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();
}
}
}
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 datatype_extensions import *
from indicator_extensions import *
class twenty_200_pips_strategy(Strategy):
"""Daily breakout: open price difference at two offsets with StartProtection SL/TP."""
def __init__(self):
super(twenty_200_pips_strategy, self).__init__()
self._tp = self.Param("TakeProfit", 200).SetGreaterThanZero().SetDisplay("Take Profit", "TP in points", "Risk")
self._sl = self.Param("StopLoss", 2000).SetGreaterThanZero().SetDisplay("Stop Loss", "SL in points", "Risk")
self._first_offset = self.Param("FirstOffset", 7).SetGreaterThanZero().SetDisplay("First Offset", "Older bar index", "Signal")
self._second_offset = self.Param("SecondOffset", 2).SetGreaterThanZero().SetDisplay("Second Offset", "Newer bar index", "Signal")
self._delta = self.Param("DeltaPoints", 1).SetGreaterThanZero().SetDisplay("Delta", "Min difference", "Signal")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(twenty_200_pips_strategy, self).OnReseted()
self._opens = []
def OnStarted2(self, time):
super(twenty_200_pips_strategy, self).OnStarted2(time)
self._opens = []
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
sec = self.Security
point_value = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and sec.PriceStep > 0 else 1.0
sl = self._sl.Value
tp = self._tp.Value
from StockSharp.Messages import Unit, UnitTypes
tp_unit = Unit(tp * point_value, UnitTypes.Absolute) if tp > 0 else None
sl_unit = Unit(sl * point_value, UnitTypes.Absolute) if sl > 0 else None
self.StartProtection(tp_unit, sl_unit)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
self._opens.append(float(candle.OpenPrice))
max_offset = max(self._first_offset.Value, self._second_offset.Value)
if len(self._opens) <= max_offset:
return
# Trim buffer
if len(self._opens) > max_offset + 100:
self._opens = self._opens[-(max_offset + 50):]
if self.Position != 0:
return
open_first = self._opens[len(self._opens) - 1 - self._first_offset.Value]
open_second = self._opens[len(self._opens) - 1 - self._second_offset.Value]
threshold = self._delta.Value
if open_first > open_second + threshold:
self.SellMarket()
elif open_first + threshold < open_second:
self.BuyMarket()
def CreateClone(self):
return twenty_200_pips_strategy()