在 GitHub 上查看
AnyRange Cloud Tail System Tm Plus 策略
该策略在 StockSharp 高级 API 中重现 Exp_i-AnyRangeCldTail_System_Tm_Plus.mq5 专家顾问的行为。它在两个自定义时间之间构建日内区间,等待价格突破该区间,然后按照与 MQL 原版相同的延迟逻辑(SignalBar 位移)在突破后的指定根 K 线执行订单。
策略支持双向交易,并暴露了控制突破权限、止损/止盈点数、持仓时间以及指标计算窗口的参数。此外,时间退出机制会在持仓超过设定分钟数后自动平仓,对应原专家顾问中对头寸的巡检逻辑。
交易逻辑
区间构建
- 使用
RangeStartTime 和 RangeEndTime 定义计算参考区间的会话窗口。
- 对于每一个已完成的交易日,策略记录该窗口内的最高价与最低价。如果
RangeStartTime 晚于 RangeEndTime,窗口会自动跨越午夜,与原始指标一致。
- 在新的日内区间生成前,将持续使用最近一次完成的区间。
突破识别
- 每根已完成 K 线都会与存储的区间比较。
- 收盘价高于区间上沿的 K 线被赋予与 MQL 指标相同的颜色编码(2 或 3),收盘价低于区间下沿的 K 线被赋予编码 0 或 1,区间内部的 K 线使用编码 4。
SignalBar 参数控制信号的偏移量:策略检查距离当前 SignalBar + 1 根的历史 K 线,并确认更近一根 K 线(SignalBar)没有重复相同的颜色。这样可以复现原专家顾问在突破后的下一根 K 线执行订单的时序。
入场
- 做多:当
AllowBuyEntry 为真,并且信号 K 线出现多头颜色(2 或 3)且后一根 K 线不再保持该颜色时触发。
- 做空:当
AllowSellEntry 为真,并且信号 K 线出现空头颜色(0 或 1)且后一根 K 线不再保持该颜色时触发。
- 如果当前持有反向头寸,会在新的市价单中叠加其仓位数量,实现立即反手,与
TradeAlgorithms.mqh 中的辅助函数行为一致。
离场
- 反向信号:若
AllowBuyExit 为真,信号 K 线出现空头颜色(0 或 1)时平掉多头;若 AllowSellExit 为真,信号 K 线出现多头颜色(2 或 3)时平掉空头。
- 时间退出:当
UseTimeExit 为真且持仓时间超过 ExitAfterMinutes 时自动平仓,对应原专家中基于 nTime 的时间限制。
- 止损/止盈:
StopLossPoints 与 TakeProfitPoints 以价格步长为单位配置止损/止盈,值为 0 时表示禁用。
风险控制
- 所有订单使用
OrderVolume 指定的基础手数。若需要反手,系统会自动加上原有仓位数量。
- 调用
StartProtection 后即刻注册止损/止盈保护,与 StockSharp 的 OCO 机制集成。
参数说明
| 参数 |
说明 |
默认值 |
OrderVolume |
新建仓位的基础手数。 |
0.1 |
AllowBuyEntry |
允许在上沿突破时开多。 |
true |
AllowSellEntry |
允许在下沿突破时开空。 |
true |
AllowBuyExit |
允许在下沿突破时平多。 |
true |
AllowSellExit |
允许在上沿突破时平空。 |
true |
UseTimeExit |
启用时间退出机制。 |
true |
ExitAfterMinutes |
持仓时间上限(分钟)。 |
1500 |
StopLossPoints |
止损点数(按价格步长计算),0 表示禁用。 |
1000 |
TakeProfitPoints |
止盈点数(按价格步长计算),0 表示禁用。 |
2000 |
SignalBar |
信号偏移量,对应 MQL 中的 SignalBar。 |
1 |
RangeLookbackDays |
搜索已完成区间时回溯的最大天数,设为 0 表示仅使用最近的区间。 |
1 |
RangeStartTime |
区间开始时间(TimeSpan)。 |
02:00 |
RangeEndTime |
区间结束时间(TimeSpan)。 |
07:00 |
CandleType |
用于计算的 K 线类型/周期。 |
30 分钟 |
实现要点
- 使用
SubscribeCandles + WhenNew 处理完成的 K 线,完全对齐原策略基于 IsNewBar 的逻辑。
- 区间值保存在轻量结构体中,并通过手动循环计算最大/最小,避免对整个集合使用 LINQ,以满足项目规范。
- 时间退出分别跟踪多头和空头的建仓时间,逻辑与 MQL 版本逐笔检查头寸一致。
OrderVolume 同步写入基类 Strategy.Volume 属性,方便在界面中观察和调整。
- 代码包含英文注释,便于再次开发或移植。
使用建议
- 请确保行情源提供与
CandleType 对齐的 K 线数据;策略基于已完成 K 线判断突破,不应用于未完成或 Tick 级别数据。
- 不同市场的交易时段各异,可根据品种调整
RangeStartTime/RangeEndTime,选择最能代表震荡区间的时间段。
- 对于价格步长不规则的品种,建议在图表或成交明细中检查实际止损/止盈价格,确认点数换算无误。
- 在更短周期运行策略时,可适当降低
ExitAfterMinutes,避免持仓时间过长。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// AnyRange Cloud Tail System TM Plus strategy. Uses Highest/Lowest channel midline crossover.
/// </summary>
public class AnyRangeCloudTailSystemTmPlusStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private decimal? _prevMid;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
public AnyRangeCloudTailSystemTmPlusStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_period = Param(nameof(Period), 20)
.SetGreaterThanZero()
.SetDisplay("Period", "Channel lookback period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMid = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMid = null;
var highest = new Highest { Length = Period };
var lowest = new Lowest { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, highest);
DrawIndicator(area, lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var mid = (high + low) / 2m;
var close = candle.ClosePrice;
if (_prevMid == null)
{
_prevMid = mid;
return;
}
// Close crosses above midline → buy
if (close > mid && candle.OpenPrice <= _prevMid.Value && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Close crosses below midline → sell
else if (close < mid && candle.OpenPrice >= _prevMid.Value && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_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 CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class any_range_cloud_tail_system_tm_plus_strategy(Strategy):
"""
AnyRange Cloud Tail System TM Plus strategy.
Uses Highest/Lowest channel midline crossover for entries.
"""
def __init__(self):
super(any_range_cloud_tail_system_tm_plus_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(60)) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 20) \
.SetGreaterThanZero() \
.SetDisplay("Period", "Channel lookback period", "Indicators")
self._prev_mid = None
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, v): self._candle_type.Value = v
@property
def Period(self): return self._period.Value
@Period.setter
def Period(self, v): self._period.Value = v
def OnReseted(self):
super(any_range_cloud_tail_system_tm_plus_strategy, self).OnReseted()
self._prev_mid = None
def OnStarted2(self, time):
super(any_range_cloud_tail_system_tm_plus_strategy, self).OnStarted2(time)
self._prev_mid = None
highest = Highest()
highest.Length = self.Period
lowest = Lowest()
lowest.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, highest)
self.DrawIndicator(area, lowest)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, high, low):
if candle.State != CandleStates.Finished:
return
mid = (high + low) / 2.0
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
if self._prev_mid is None:
self._prev_mid = mid
return
if close > mid and open_price <= self._prev_mid and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close < mid and open_price >= self._prev_mid and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_mid = mid
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return any_range_cloud_tail_system_tm_plus_strategy()