CGOscillator X2 策略
概述
CGOscillator X2 策略是一套基于重心振荡指标(Center of Gravity Oscillator)的多周期顺势系统。策略先在更高周期上评估振荡器的斜率来确认主导趋势,再等待较低周期出现回调钩形信号后顺势入场。策略提供以绝对价格单位表示的止损与止盈参数,允许在开仓后即时设置风险控制。
交易逻辑
- 趋势识别(高周期)
- 在趋势周期上使用参数
TrendLength计算 CG 振荡器。 - 当前 CG 值高于信号线(上一周期的 CG 值)时判定为多头趋势;低于信号线时判定为空头趋势。
- 在趋势周期上使用参数
- 信号生成(低周期)
- 在信号周期上使用独立长度的 CG 振荡器。
- 策略比较最近两个已完成 K 线:若当前 CG ≥ 信号且上一根 CG < 上一根信号,则视为下跌趋势中的多头钩形;若当前 CG ≤ 信号且上一根 CG > 上一根信号,则视为上涨趋势中的空头钩形。
- 开仓与平仓
- 仅当高周期显示上升趋势且低周期出现空头钩形(回调到超卖区)时才允许做多;做空逻辑为镜像条件。
- 依据布尔参数可选择在高周期趋势翻转时平仓,或在低周期钩形反向时立即平仓。
- 风险控制
- 每次市价开仓后都会根据参数设置绝对距离的止损和止盈。当价格在当前 K 线内触发止损或止盈时,策略会先行平仓再处理新的信号。
参数说明
| 名称 | 说明 |
|---|---|
TrendCandleType |
用于趋势分析的蜡烛类型(时间框架)。 |
SignalCandleType |
用于信号分析的蜡烛类型。 |
TrendLength |
趋势周期 CG 振荡器的长度。 |
SignalLength |
信号周期 CG 振荡器的长度。 |
BuyOpen |
是否允许在上升趋势内开多仓。 |
SellOpen |
是否允许在下降趋势内开空仓。 |
BuyClose |
高周期趋势转为空头时是否平掉多单。 |
SellClose |
高周期趋势转为多头时是否平掉空单。 |
BuyCloseSignal |
低周期出现空头钩形时是否平掉多单。 |
SellCloseSignal |
低周期出现多头钩形时是否平掉空单。 |
StopLoss |
以绝对价格表示的止损距离,0 表示不启用。 |
TakeProfit |
以绝对价格表示的止盈距离,0 表示不启用。 |
指标细节
自定义的 CenterOfGravityOscillatorIndicator 完全复刻 MT5 版本的 CG 振荡器:
- 使用
(最高价 + 最低价) / 2的中位价作为输入。 - 对最近
Length根中位价进行加权求和得到 CG 值。 - 信号线是上一周期的 CG 值,用于识别钩形拐点。
使用提示
- 通过设置策略的
Volume属性来确定基础下单数量。若需要反手,策略会自动在市价单中加入当前仓位的绝对值,确保新仓方向正确。 - 策略仅在 K 线收盘后做出决策,能够减少盘中噪音干扰,但会在每根 K 线收盘时才反应。
- 止损与止盈采用绝对价格单位,请根据标的品种的最小变动价位与波动性进行调整。
- 只需配置合适的蜡烛类型,该策略即可用于 StockSharp 支持的任何交易品种。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades pullbacks using the Center of Gravity oscillator on two timeframes.
/// </summary>
public class CgOscillatorX2Strategy : Strategy
{
private readonly StrategyParam<DataType> _trendCandleType;
private readonly StrategyParam<DataType> _signalCandleType;
private readonly StrategyParam<int> _trendLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellClose;
private readonly StrategyParam<bool> _buyCloseSignal;
private readonly StrategyParam<bool> _sellCloseSignal;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<int> _signalCooldownBars;
private CenterOfGravityOscillator _trendIndicator;
private CenterOfGravityOscillator _signalIndicator;
private int _trendDirection;
private decimal? _trendPrevCg;
private decimal? _signalPrevCg;
private decimal? _signalPrevPrevCg;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private int _cooldownRemaining;
public DataType TrendCandleType { get => _trendCandleType.Value; set => _trendCandleType.Value = value; }
public DataType SignalCandleType { get => _signalCandleType.Value; set => _signalCandleType.Value = value; }
public int TrendLength { get => _trendLength.Value; set => _trendLength.Value = value; }
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
public bool BuyOpen { get => _buyOpen.Value; set => _buyOpen.Value = value; }
public bool SellOpen { get => _sellOpen.Value; set => _sellOpen.Value = value; }
public bool BuyClose { get => _buyClose.Value; set => _buyClose.Value = value; }
public bool SellClose { get => _sellClose.Value; set => _sellClose.Value = value; }
public bool BuyCloseSignal { get => _buyCloseSignal.Value; set => _buyCloseSignal.Value = value; }
public bool SellCloseSignal { get => _sellCloseSignal.Value; set => _sellCloseSignal.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public CgOscillatorX2Strategy()
{
_trendCandleType = Param(nameof(TrendCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Trend Candle Type", "Higher timeframe for trend detection", "General");
_signalCandleType = Param(nameof(SignalCandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Signal Candle Type", "Lower timeframe for trade execution", "General");
_trendLength = Param(nameof(TrendLength), 10)
.SetGreaterThanZero()
.SetDisplay("Trend Length", "CG length on the trend timeframe", "Indicator");
_signalLength = Param(nameof(SignalLength), 10)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "CG length on the signal timeframe", "Indicator");
_buyOpen = Param(nameof(BuyOpen), true)
.SetDisplay("Allow Long Entries", "Enable long entries during uptrend", "Trading");
_sellOpen = Param(nameof(SellOpen), true)
.SetDisplay("Allow Short Entries", "Enable short entries during downtrend", "Trading");
_buyClose = Param(nameof(BuyClose), true)
.SetDisplay("Close Long On Trend Flip", "Exit long positions when higher trend turns bearish", "Trading");
_sellClose = Param(nameof(SellClose), true)
.SetDisplay("Close Short On Trend Flip", "Exit short positions when higher trend turns bullish", "Trading");
_buyCloseSignal = Param(nameof(BuyCloseSignal), false)
.SetDisplay("Close Long On Pullback", "Exit long positions when the oscillator confirms a bearish hook", "Trading");
_sellCloseSignal = Param(nameof(SellCloseSignal), false)
.SetDisplay("Close Short On Pullback", "Exit short positions when the oscillator confirms a bullish hook", "Trading");
_stopLoss = Param(nameof(StopLoss), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss Distance", "Absolute stop-loss distance in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 0m)
.SetNotNegative()
.SetDisplay("Take Profit Distance", "Absolute take-profit distance in price units", "Risk");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 6)
.SetNotNegative()
.SetDisplay("Signal Cooldown Bars", "Closed signal candles to wait before a new entry", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TrendCandleType);
if (!TrendCandleType.Equals(SignalCandleType))
yield return (Security, SignalCandleType);
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_trendIndicator = new CenterOfGravityOscillator
{
Length = TrendLength
};
_signalIndicator = new CenterOfGravityOscillator
{
Length = SignalLength
};
SubscribeCandles(TrendCandleType)
.BindEx(_trendIndicator, ProcessTrend)
.Start();
SubscribeCandles(SignalCandleType)
.BindEx(_signalIndicator, ProcessSignal)
.Start();
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trendDirection = 0;
_trendPrevCg = null;
_signalPrevCg = null;
_signalPrevPrevCg = null;
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
_cooldownRemaining = 0;
}
private void ProcessTrend(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!_trendIndicator.IsFormed)
return;
var cgValue = value.GetValue<decimal>();
var prevCg = _trendPrevCg;
_trendPrevCg = cgValue;
if (cgValue > 0)
_trendDirection = 1;
else if (cgValue < 0)
_trendDirection = -1;
else
_trendDirection = 0;
}
private void ProcessSignal(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!_signalIndicator.IsFormed)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var cgValue = value.GetValue<decimal>();
var prevCg = _signalPrevCg;
var prevPrevCg = _signalPrevPrevCg;
_signalPrevPrevCg = _signalPrevCg;
_signalPrevCg = cgValue;
if (prevCg is null)
return;
if (TryCloseByRisk(candle))
return;
var closeBuy = BuyCloseSignal && prevCg < 0;
var closeSell = SellCloseSignal && prevCg > 0;
var openBuy = false;
var openSell = false;
var bullishHook = prevPrevCg.HasValue && prevPrevCg.Value >= prevCg && cgValue > prevCg;
var bearishHook = prevPrevCg.HasValue && prevPrevCg.Value <= prevCg && cgValue < prevCg;
if (_trendDirection < 0)
{
if (BuyClose)
closeBuy = true;
if (_cooldownRemaining == 0 && SellOpen && bearishHook)
openSell = true;
}
else if (_trendDirection > 0)
{
if (SellClose)
closeSell = true;
if (_cooldownRemaining == 0 && BuyOpen && bullishHook)
openBuy = true;
}
if (closeBuy && Position > 0)
{
SellMarket(Position);
ResetRiskTargets();
}
if (closeSell && Position < 0)
{
BuyMarket(-Position);
ResetRiskTargets();
}
if (openBuy && Position <= 0)
{
var volume = Volume + (Position < 0 ? Math.Abs(Position) : 0m);
BuyMarket(volume);
SetRiskTargets(candle.ClosePrice, true);
_cooldownRemaining = SignalCooldownBars;
}
else if (openSell && Position >= 0)
{
var volume = Volume + (Position > 0 ? Math.Abs(Position) : 0m);
SellMarket(volume);
SetRiskTargets(candle.ClosePrice, false);
_cooldownRemaining = SignalCooldownBars;
}
}
private bool TryCloseByRisk(ICandleMessage candle)
{
if (Position > 0)
{
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
ResetRiskTargets();
return true;
}
if (_takePrice is decimal take && candle.HighPrice >= take)
{
SellMarket(Position);
ResetRiskTargets();
return true;
}
}
else if (Position < 0)
{
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(-Position);
ResetRiskTargets();
return true;
}
if (_takePrice is decimal take && candle.LowPrice <= take)
{
BuyMarket(-Position);
ResetRiskTargets();
return true;
}
}
return false;
}
private void SetRiskTargets(decimal entryPrice, bool isLong)
{
_entryPrice = entryPrice;
if (StopLoss > 0m)
_stopPrice = isLong ? entryPrice - StopLoss : entryPrice + StopLoss;
else
_stopPrice = null;
if (TakeProfit > 0m)
_takePrice = isLong ? entryPrice + TakeProfit : entryPrice - TakeProfit;
else
_takePrice = null;
}
private void ResetRiskTargets()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import CenterOfGravityOscillator
from StockSharp.Algo.Strategies import Strategy
class cg_oscillator_x2_strategy(Strategy):
def __init__(self):
super(cg_oscillator_x2_strategy, self).__init__()
self._trend_candle_type = self.Param("TrendCandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._signal_candle_type = self.Param("SignalCandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._trend_length = self.Param("TrendLength", 10)
self._signal_length = self.Param("SignalLength", 10)
self._buy_open = self.Param("BuyOpen", True)
self._sell_open = self.Param("SellOpen", True)
self._buy_close = self.Param("BuyClose", True)
self._sell_close = self.Param("SellClose", True)
self._buy_close_signal = self.Param("BuyCloseSignal", False)
self._sell_close_signal = self.Param("SellCloseSignal", False)
self._stop_loss = self.Param("StopLoss", 0.0)
self._take_profit = self.Param("TakeProfit", 0.0)
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 6)
self._trend_direction = 0
self._trend_prev_cg = None
self._signal_prev_cg = None
self._signal_prev_prev_cg = None
self._entry_price = None
self._stop_price = None
self._take_price = None
self._cooldown_remaining = 0
@property
def TrendCandleType(self):
return self._trend_candle_type.Value
@TrendCandleType.setter
def TrendCandleType(self, value):
self._trend_candle_type.Value = value
@property
def SignalCandleType(self):
return self._signal_candle_type.Value
@SignalCandleType.setter
def SignalCandleType(self, value):
self._signal_candle_type.Value = value
@property
def TrendLength(self):
return self._trend_length.Value
@TrendLength.setter
def TrendLength(self, value):
self._trend_length.Value = value
@property
def SignalLength(self):
return self._signal_length.Value
@SignalLength.setter
def SignalLength(self, value):
self._signal_length.Value = value
@property
def BuyOpen(self):
return self._buy_open.Value
@BuyOpen.setter
def BuyOpen(self, value):
self._buy_open.Value = value
@property
def SellOpen(self):
return self._sell_open.Value
@SellOpen.setter
def SellOpen(self, value):
self._sell_open.Value = value
@property
def BuyClose(self):
return self._buy_close.Value
@BuyClose.setter
def BuyClose(self, value):
self._buy_close.Value = value
@property
def SellClose(self):
return self._sell_close.Value
@SellClose.setter
def SellClose(self, value):
self._sell_close.Value = value
@property
def BuyCloseSignal(self):
return self._buy_close_signal.Value
@BuyCloseSignal.setter
def BuyCloseSignal(self, value):
self._buy_close_signal.Value = value
@property
def SellCloseSignal(self):
return self._sell_close_signal.Value
@SellCloseSignal.setter
def SellCloseSignal(self, value):
self._sell_close_signal.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
@SignalCooldownBars.setter
def SignalCooldownBars(self, value):
self._signal_cooldown_bars.Value = value
def OnStarted2(self, time):
super(cg_oscillator_x2_strategy, self).OnStarted2(time)
self._trend_indicator = CenterOfGravityOscillator()
self._trend_indicator.Length = self.TrendLength
self._signal_indicator = CenterOfGravityOscillator()
self._signal_indicator.Length = self.SignalLength
self.SubscribeCandles(self.TrendCandleType).BindEx(self._trend_indicator, self._process_trend).Start()
self.SubscribeCandles(self.SignalCandleType).BindEx(self._signal_indicator, self._process_signal).Start()
def _process_trend(self, candle, value):
if candle.State != CandleStates.Finished:
return
if not self._trend_indicator.IsFormed:
return
cg_value = float(value)
self._trend_prev_cg = cg_value
if cg_value > 0:
self._trend_direction = 1
elif cg_value < 0:
self._trend_direction = -1
else:
self._trend_direction = 0
def _process_signal(self, candle, value):
if candle.State != CandleStates.Finished:
return
if not self._signal_indicator.IsFormed:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
cg_value = float(value)
prev_cg = self._signal_prev_cg
prev_prev_cg = self._signal_prev_prev_cg
self._signal_prev_prev_cg = self._signal_prev_cg
self._signal_prev_cg = cg_value
if prev_cg is None:
return
if self._try_close_by_risk(candle):
return
close_buy = self.BuyCloseSignal and prev_cg < 0
close_sell = self.SellCloseSignal and prev_cg > 0
open_buy = False
open_sell = False
bullish_hook = prev_prev_cg is not None and prev_prev_cg >= prev_cg and cg_value > prev_cg
bearish_hook = prev_prev_cg is not None and prev_prev_cg <= prev_cg and cg_value < prev_cg
if self._trend_direction < 0:
if self.BuyClose:
close_buy = True
if self._cooldown_remaining == 0 and self.SellOpen and bearish_hook:
open_sell = True
elif self._trend_direction > 0:
if self.SellClose:
close_sell = True
if self._cooldown_remaining == 0 and self.BuyOpen and bullish_hook:
open_buy = True
if close_buy and self.Position > 0:
self.SellMarket()
self._reset_risk_targets()
if close_sell and self.Position < 0:
self.BuyMarket()
self._reset_risk_targets()
close = float(candle.ClosePrice)
if open_buy and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._set_risk_targets(close, True)
self._cooldown_remaining = int(self.SignalCooldownBars)
elif open_sell and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._set_risk_targets(close, False)
self._cooldown_remaining = int(self.SignalCooldownBars)
def _try_close_by_risk(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._stop_price is not None and low <= self._stop_price:
self.SellMarket()
self._reset_risk_targets()
return True
if self._take_price is not None and high >= self._take_price:
self.SellMarket()
self._reset_risk_targets()
return True
elif self.Position < 0:
if self._stop_price is not None and high >= self._stop_price:
self.BuyMarket()
self._reset_risk_targets()
return True
if self._take_price is not None and low <= self._take_price:
self.BuyMarket()
self._reset_risk_targets()
return True
return False
def _set_risk_targets(self, entry_price, is_long):
self._entry_price = entry_price
sl = float(self.StopLoss)
tp = float(self.TakeProfit)
if sl > 0.0:
self._stop_price = entry_price - sl if is_long else entry_price + sl
else:
self._stop_price = None
if tp > 0.0:
self._take_price = entry_price + tp if is_long else entry_price - tp
else:
self._take_price = None
def _reset_risk_targets(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(cg_oscillator_x2_strategy, self).OnReseted()
self._trend_direction = 0
self._trend_prev_cg = None
self._signal_prev_cg = None
self._signal_prev_prev_cg = None
self._entry_price = None
self._stop_price = None
self._take_price = None
self._cooldown_remaining = 0
def CreateClone(self):
return cg_oscillator_x2_strategy()