在 GitHub 上查看
CCI and Martin 策略
概述
CCI and Martin 策略在短期的单向行情后寻找快速反转信号,并使用商品通道指数(CCI)进行确认。该实现完全复刻原始 MetaTrader 5 专家顾问的交易逻辑,同时基于 StockSharp 的高级 API,仅处理已经完成的 K 线,可用于任意提供 CCI 与价格步长信息的交易品种。
交易规则
- 做多条件
-2 和 -1 两根 K 线必须为阴线(开盘价高于收盘价)。
- 当前 K 线
0 必须收在开盘价之上,并且高于前一根 K 线 -1 的开盘价。
- CCI 在 K 线
-1 上需要低于 +5,低于 -2 的数值,并且 -2 与 -3 的 CCI 必须形成下降序列;当前 CCI(K 线 0)需要向上拐头并高于 -1 的值。
- 满足以上条件且没有持仓时,开多单。
- 做空条件
-2 和 -1 两根 K 线必须为阳线(开盘价低于收盘价)。
- 当前 K 线
0 必须收在开盘价之下,并且低于前一根 K 线 -1 的开盘价。
- CCI 在 K 线
-1 上需要高于 -5,高于 -2 的数值,并且 -2 与 -3 的 CCI 必须形成上升序列;当前 CCI(K 线 0)需要向下拐头并低于 -1 的值。
- 满足以上条件且没有持仓时,开空单。
原版 EA 会在新一分钟开始后等待 40 秒以避免未完成的 K 线;本实现只处理收盘后的 K 线,因此无需额外延迟。
风险控制
- 止损 和 止盈 以“点”表示。程序会根据合约的价格步长自动换算为价格偏移量:对于 3 位或 5 位报价的品种,点值等于价格步长乘以 10,与原始策略一致。
- 移动止损 在价格运行超过“移动止损距离 + 移动步长”后启动,随后按照移动止损距离跟踪价格,仅当价格进一步改善超过移动步长时才会继续上移(或下移)。
- 当止损或止盈为零时,对应的保护措施会被禁用。要启用移动止损,距离和步长都必须为正值。
仓位管理
策略提供两个可选的仓位调整机制。
- 马丁格尔:当连续亏损次数达到阈值时,将当前交易量乘以马丁系数。放大次数不会超过预设的最大步数,任意一次盈利都会把交易量重置为初始值。
- 阶梯加仓:根据设定模式在亏损后或盈利后增加固定数量的交易量。增加的数量会按合约的最小交易单位归一化,并受最大交易量限制;超过上限或条件不满足时,交易量回到初始值。
与原策略一致,马丁格尔与阶梯加仓无法同时启用,代码会抛出异常提醒。
参数
CandleType – 使用的 K 线类型。
CciPeriod – CCI 指标的计算周期。
InitialVolume – 初始下单数量。
StopLossPips – 止损距离(点)。
TakeProfitPips – 止盈距离(点)。
TrailingStopPips – 移动止损距离(点,0 表示关闭)。
TrailingStepPips – 移动止损的最小触发步长。
EnableMartingale – 是否启用马丁格尔放大。
MartingaleCoefficient – 马丁格尔放大系数。
MartingaleTriggerLosses – 触发马丁格尔所需的连续亏损次数。
MartingaleMaxSteps – 马丁格尔最多执行的步数。
EnableStepAdjustments – 是否启用阶梯加仓。
StepVolumeIncrement – 阶梯加仓的增量。
StepVolumeMax – 阶梯加仓允许的最大交易量。
StepAdjustmentMode – 阶梯加仓触发模式(亏损后或盈利后)。
注意事项
- 策略假设市价单能够以接近请求的价格成交。为了模拟原版的逐笔移动止损,止损价会在每根完成的 K 线上重新计算。
- 如果品种的价格步长与常见外汇报价不同,点值换算仍然有效,但每一点对应的金额可能有所差异。
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>
/// CCI based strategy with martingale-style entry.
/// Buys when CCI crosses above oversold level, sells when CCI crosses below overbought level.
/// </summary>
public class CCIAndMartinStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<decimal> _oversold;
private decimal? _prevCci;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
public CCIAndMartinStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_cciPeriod = Param(nameof(CciPeriod), 27)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI indicator length", "Indicators");
_overbought = Param(nameof(Overbought), 100m)
.SetDisplay("Overbought", "CCI overbought level", "Indicators");
_oversold = Param(nameof(Oversold), -100m)
.SetDisplay("Oversold", "CCI oversold level", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = null;
var cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, cci);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevCci = cciValue;
return;
}
if (_prevCci == null)
{
_prevCci = cciValue;
return;
}
var prev = _prevCci.Value;
_prevCci = cciValue;
// Buy signal: CCI crosses above oversold level from below
if (prev < Oversold && cciValue >= Oversold && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Sell signal: CCI crosses below overbought level from above
else if (prev > Overbought && cciValue <= Overbought && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
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.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_and_martin_strategy(Strategy):
def __init__(self):
super(cci_and_martin_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._cci_period = self.Param("CciPeriod", 27) \
.SetDisplay("CCI Period", "CCI indicator length", "Indicators")
self._overbought = self.Param("Overbought", 100.0) \
.SetDisplay("Overbought", "CCI overbought level", "Indicators")
self._oversold = self.Param("Oversold", -100.0) \
.SetDisplay("Oversold", "CCI oversold level", "Indicators")
self._prev_cci = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def CciPeriod(self):
return self._cci_period.Value
@property
def Overbought(self):
return self._overbought.Value
@property
def Oversold(self):
return self._oversold.Value
def OnReseted(self):
super(cci_and_martin_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(cci_and_martin_strategy, self).OnStarted2(time)
self._prev_cci = None
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, cci)
self.DrawOwnTrades(area)
def _on_process(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
cv = float(cci_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_cci = cv
return
if self._prev_cci is None:
self._prev_cci = cv
return
prev = self._prev_cci
self._prev_cci = cv
ob = float(self.Overbought)
os_level = float(self.Oversold)
# Buy signal: CCI crosses above oversold level from below
if prev < os_level and cv >= os_level and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Sell signal: CCI crosses below overbought level from above
elif prev > ob and cv <= ob and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return cci_and_martin_strategy()