在 GitHub 上查看
One-Two-Three Pattern 策略
概述
本策略复刻了 Martes 在 MetaTrader 4 中的专家顾问“1-2-3_forCodeBase_v01.mq4”。它在收盘价上寻找经典的 1-2-3 反转形态:两个连续的趋势段配合一个回撤段。移植版本完整保留了原有规则,包括自定义的趋势长度指标(RelDownTrLen_forCodeBase_v01、RelUpTrLen_forCodeBase_v01)以及 MACD 过滤条件。
做多信号要求:当前价格附近出现新的低点(点 3),其前方存在高点(点 2),再往前有低点(点 1)。上一段下跌趋势的长度必须至少是当前上行回撤的 TrendRatio 倍,同时 MACD 需向上穿越信号线(或零轴),并且在点 3 处保持为正值。做空逻辑完全镜像。止损放在点 3 之外一个最小跳动,止盈等于上一段摆动的高度;若启用按点数的追踪止损,则当浮盈达到设定距离时向有利方向移动止损。
交易规则
- 订阅指定类型的 K 线(
CandleType),并按收盘价计算 MACD(快/慢/信号周期)。
- 维护一个滚动的 K 线实体缓冲区,用于识别 1-2-3 结构。实体最低点视为谷值,实体最高点视为峰值。
- 按照原始自定义指标的凸包算法计算趋势长度,并将结果归一化到
[0,1]。多头要求最近的下跌段长度至少是上一段上升段的 TrendRatio 倍(空头反之)。
- 用 MACD 确认信号:
- 多头:MACD 向上穿越信号线(或零轴),且点 3 处的 MACD 值为正。
- 空头:MACD 向下穿越信号线(或零轴),且点 3 处的 MACD 值为负。
- 额外过滤条件:
- 当前价格距离点 2 不得超过 5 个点。
- 预期止损距离
|point2 - point3| 必须不少于 13 个点。
TakeProfitPips 保持 ≥ 10,否则策略停止交易(与原版相同的安全检查)。
- 委托管理:
- 使用
BuyMarket/SellMarket 按 TradeVolume 手数下单(若反手交易,会把当前仓位数量加上)。
- 初始止损 = 点 3 ± 一个最小价格步长。
- 止盈 = 入场价 ±
|point2 - point3|。
- 当
TrailingStopPips > 0 且浮盈超过该距离后,将止损沿趋势方向移动相同的点数。
- 当触发止损、止盈或追踪止损时平仓。策略同一时间只持有一个方向的仓位。
参数
| 参数 |
类型 |
默认值 |
说明 |
TakeProfitPips |
decimal |
60 |
与原版一致的兼容参数,若小于 10 则禁止交易。 |
TradeVolume |
decimal |
0.5 |
每次下单的 MetaTrader 手数。 |
TrailingStopPips |
decimal |
30 |
追踪止损距离(点)。设为 0 关闭追踪。 |
TrendRatio |
decimal |
4 |
主趋势长度与回撤长度的最小比值。 |
CandleType |
DataType |
H1 |
用于识别形态和计算 MACD 的 K 线类型。 |
MacdFast |
int |
12 |
MACD 快速 EMA 周期。 |
MacdSlow |
int |
26 |
MACD 慢速 EMA 周期。 |
MacdSignal |
int |
9 |
MACD 信号线周期。 |
PatternLookback |
int |
100 |
搜索 1-2-3 形态时最多回溯的历史 K 线数量。 |
实现说明
RelDownTrLen 与 RelUpTrLen 指标逻辑一字不差地移植:对 K 线实体的凸包寻找最长单调区间,并输出其相对长度([0,1])。该值用于趋势强度过滤。
- 为控制内存开销,历史 K 线与 MACD 值分别保存在长度最多 600 的环形缓冲中,同时保证足够的分析深度。
- 止损与止盈完全按 MetaTrader 的方式手动维护:与当根 K 线的高/低比较,追踪止损只有在价格前进到设定距离后才会收紧。
- 在
OnReseted 与 OnStarted 中同步 Volume = TradeVolume,以便优化器沿用标准的策略属性。
参考
- 原始 MQL4 策略:
MQL/8131/1-2-3_forCodeBase_v01.mq4。
- 自定义指标:
RelDownTrLen_forCodeBase_v01.mq4、RelUpTrLen_forCodeBase_v01.mq4。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class OneTwoThreePatternStrategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose; private decimal _prevMid; private bool _hasPrev;
private int _cooldown;
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public OneTwoThreePatternStrategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 20).SetDisplay("Channel Period", "Pattern lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = default;
_prevMid = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var highest = new Highest { Length = ChannelPeriod };
var lowest = new Lowest { Length = ChannelPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(highest, lowest, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) return;
var close = candle.ClosePrice;
var mid = (highest + lowest) / 2;
if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }
if (_cooldown > 0)
{
_cooldown--;
_prevClose = close; _prevMid = mid;
return;
}
if (_prevClose <= _prevMid && close > mid && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = 6;
}
else if (_prevClose >= _prevMid && close < mid && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = 6;
}
_prevClose = close; _prevMid = mid;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class one_two_three_pattern_strategy(Strategy):
def __init__(self):
super(one_two_three_pattern_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 20).SetDisplay("Channel Period", "Pattern lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0; self._prev_mid = 0.0; self._has_prev = False; self._cooldown = 0
@property
def channel_period(self): return self._channel_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(one_two_three_pattern_strategy, self).OnReseted()
self._prev_close = 0.0; self._prev_mid = 0.0; self._has_prev = False; self._cooldown = 0
def OnStarted2(self, time):
super(one_two_three_pattern_strategy, self).OnStarted2(time)
self._has_prev = False; self._cooldown = 0
highest = Highest(); highest.Length = self.channel_period
lowest = Lowest(); lowest.Length = self.channel_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, self.process_candle).Start()
def process_candle(self, candle, highest, lowest):
if candle.State != CandleStates.Finished: return
if not self.IsFormedAndOnlineAndAllowTrading(): return
close = float(candle.ClosePrice)
mid = (float(highest) + float(lowest)) / 2.0
if not self._has_prev:
self._prev_close = close; self._prev_mid = mid; self._has_prev = True; return
if self._cooldown > 0:
self._cooldown -= 1; self._prev_close = close; self._prev_mid = mid; return
if self._prev_close <= self._prev_mid and close > mid and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume); self._cooldown = 6
elif self._prev_close >= self._prev_mid and close < mid and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume); self._cooldown = 6
self._prev_close = close; self._prev_mid = mid
def CreateClone(self): return one_two_three_pattern_strategy()