Color Schaff TRIX Trend Cycle 策略
该策略实现了基于 TRIX 和 MACD 的 Schaff Trend Cycle 振荡指标。当振荡器跨越预设水平时,识别趋势的周期性变化并产生交易信号。
工作原理
- 计算两个不同周期的 TRIX 振荡器以构建 MACD 序列。
- MACD 值经过两次随机指标变换得到 Schaff Trend Cycle (STC)。
- 当 STC 上穿高位水平时开多头仓位,下穿低位水平时开空头仓位。
- 当出现反向交叉时平掉现有仓位。
参数
- Fast TRIX – 快速 TRIX 的周期。
- Slow TRIX – 慢速 TRIX 的周期。
- Cycle – 随机指标计算周期。
- High Level / Low Level – STC 的上下界。
- Stop Loss % / Take Profit % – 风险控制参数,以百分比表示。
- Buy/Sell Open/Close – 控制是否允许开仓或平仓。
说明
策略使用所选时间框架的K线数据(默认4小时)并以市价单执行。启用了带有止损和止盈的保护。所有指标处理均通过高级 API 自动完成。
本策略仅用于学习示例,实际交易前请务必充分回测。
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Schaff Trend Cycle calculated over TRIX MACD.
/// Buys when the oscillator crosses above the high level and sells when it
/// crosses below the low level.
/// </summary>
public class ColorSchaffTrixTrendCycleStrategy : Strategy {
private readonly StrategyParam<int> _fastTrixLength;
private readonly StrategyParam<int> _slowTrixLength;
private readonly StrategyParam<int> _cycle;
private readonly StrategyParam<int> _highLevel;
private readonly StrategyParam<int> _lowLevel;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellClose;
private readonly StrategyParam<decimal> _factor;
private readonly StrategyParam<int> _signalCooldownBars;
private SchaffTrixTrendCycle _stc = null!;
private decimal? _prevStc;
private int _cooldownRemaining;
/// <summary>
/// Fast TRIX length.
/// </summary>
public int FastTrixLength {
get => _fastTrixLength.Value;
set => _fastTrixLength.Value = value;
}
/// <summary>
/// Slow TRIX length.
/// </summary>
public int SlowTrixLength {
get => _slowTrixLength.Value;
set => _slowTrixLength.Value = value;
}
/// <summary>
/// Cycle length used in stochastic calculations.
/// </summary>
public int Cycle {
get => _cycle.Value;
set => _cycle.Value = value;
}
/// <summary>
/// Upper threshold for oscillator.
/// </summary>
public int HighLevel {
get => _highLevel.Value;
set => _highLevel.Value = value;
}
/// <summary>
/// Lower threshold for oscillator.
/// </summary>
public int LowLevel {
get => _lowLevel.Value;
set => _lowLevel.Value = value;
}
/// <summary>
/// Candle type for indicator calculation.
/// </summary>
public DataType CandleType {
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop loss value in percent.
/// </summary>
public decimal StopLoss {
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take profit value in percent.
/// </summary>
public decimal TakeProfit {
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyOpen {
get => _buyOpen.Value;
set => _buyOpen.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellOpen {
get => _sellOpen.Value;
set => _sellOpen.Value = value;
}
/// <summary>
/// Allow closing long positions.
/// </summary>
public bool BuyClose {
get => _buyClose.Value;
set => _buyClose.Value = value;
}
/// <summary>
/// Allow closing short positions.
/// </summary>
public bool SellClose {
get => _sellClose.Value;
set => _sellClose.Value = value;
}
/// <summary>
/// Smoothing factor used in the Schaff Trend Cycle calculations.
/// </summary>
public decimal Factor {
get => _factor.Value;
set => _factor.Value = value;
}
/// <summary>
/// Bars to wait between reversals.
/// </summary>
public int SignalCooldownBars {
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of <see
/// cref="ColorSchaffTrixTrendCycleStrategy"/>.
/// </summary>
public ColorSchaffTrixTrendCycleStrategy() {
_fastTrixLength =
Param(nameof(FastTrixLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast TRIX", "Fast TRIX length", "Indicator")
.SetOptimize(3, 15, 2);
_slowTrixLength =
Param(nameof(SlowTrixLength), 12)
.SetGreaterThanZero()
.SetDisplay("Slow TRIX", "Slow TRIX length", "Indicator")
.SetOptimize(8, 30, 3);
_cycle = Param(nameof(Cycle), 10)
.SetGreaterThanZero()
.SetDisplay("Cycle", "Cycle length", "Indicator")
.SetOptimize(5, 20, 1);
_highLevel =
Param(nameof(HighLevel), 20)
.SetDisplay("High Level", "Upper threshold", "Indicator");
_lowLevel =
Param(nameof(LowLevel), -20)
.SetDisplay("Low Level", "Lower threshold", "Indicator");
_candleType =
Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
_stopLoss =
Param(nameof(StopLoss), 1m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfit =
Param(nameof(TakeProfit), 2m)
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
_buyOpen = Param(nameof(BuyOpen), true)
.SetDisplay("Buy Open", "Allow buy entries", "Trading");
_sellOpen =
Param(nameof(SellOpen), true)
.SetDisplay("Sell Open", "Allow sell entries", "Trading");
_buyClose =
Param(nameof(BuyClose), true)
.SetDisplay("Buy Close", "Allow closing long", "Trading");
_sellClose =
Param(nameof(SellClose), true)
.SetDisplay("Sell Close", "Allow closing short", "Trading");
_factor =
Param(nameof(Factor), 0.5m)
.SetDisplay("Factor", "Smoothing factor for STC calculations", "Indicator");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 8)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Trading");
}
/// <inheritdoc />
protected override void OnReseted() {
base.OnReseted();
_prevStc = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time) {
base.OnStarted2(time);
_stc = new SchaffTrixTrendCycle { FastLength = FastTrixLength,
SlowLength = SlowTrixLength,
Cycle = Cycle,
Factor = Factor };
_prevStc = null;
_cooldownRemaining = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_stc, ProcessStc).Start();
StartProtection(stopLoss: new Unit(StopLoss, UnitTypes.Percent),
takeProfit: new Unit(TakeProfit, UnitTypes.Percent));
}
private void ProcessStc(ICandleMessage candle, decimal stc) {
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (_prevStc is null) {
_prevStc = stc;
return;
}
var prev = _prevStc.Value;
var crossedUp = stc > HighLevel && prev <= HighLevel;
var crossedDown = stc < LowLevel && prev >= LowLevel;
var longExit = BuyClose && Position > 0 && stc < 0m;
var shortExit = SellClose && Position < 0 && stc > 0m;
if (longExit) {
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
} else if (shortExit) {
BuyMarket(Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
} else if (_cooldownRemaining == 0 && crossedUp && BuyOpen && Position <= 0) {
BuyMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
} else if (_cooldownRemaining == 0 && crossedDown && SellOpen && Position >= 0) {
SellMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
_prevStc = stc;
}
/// <summary>
/// Schaff Trend Cycle indicator based on TRIX MACD.
/// </summary>
private sealed class SchaffTrixTrendCycle : BaseIndicator {
public int FastLength { get; set; }
public int SlowLength { get; set; }
public int Cycle { get; set; }
public decimal Factor { get; set; } = 0.5m;
private Trix _fast;
private Trix _slow;
private Highest _macdHigh;
private Lowest _macdLow;
private Highest _stHigh;
private Lowest _stLow;
private decimal _stPrev;
private decimal _stcPrev;
private bool _stPass;
private bool _stcPass;
private bool _inited;
public override int NumValuesToInitialize => 1;
protected override bool CalcIsFormed() => _inited && _slow.IsFormed && _stcPass;
private void EnsureInit()
{
if (_inited) return;
_fast = new Trix(FastLength);
_slow = new Trix(SlowLength);
_macdHigh = new Highest { Length = Cycle };
_macdLow = new Lowest { Length = Cycle };
_stHigh = new Highest { Length = Cycle };
_stLow = new Lowest { Length = Cycle };
_inited = true;
}
protected override IIndicatorValue OnProcess(IIndicatorValue input) {
EnsureInit();
var t = input.Time;
var fast = _fast.Process(input).ToDecimal();
var slow = _slow.Process(input).ToDecimal();
var macd = fast - slow;
var macdHigh = _macdHigh.Process(new DecimalIndicatorValue(_macdHigh, macd, t) { IsFinal = input.IsFinal }).ToDecimal();
var macdLow = _macdLow.Process(new DecimalIndicatorValue(_macdLow, macd, t) { IsFinal = input.IsFinal }).ToDecimal();
decimal st;
if (macdHigh - macdLow != 0)
st = (macd - macdLow) / (macdHigh - macdLow) * 100m;
else
st = _stPrev;
if (_stPass)
st = Factor * (st - _stPrev) + _stPrev;
_stPrev = st;
_stPass = true;
var stHigh = _stHigh.Process(new DecimalIndicatorValue(_stHigh, st, t) { IsFinal = input.IsFinal }).ToDecimal();
var stLow = _stLow.Process(new DecimalIndicatorValue(_stLow, st, t) { IsFinal = input.IsFinal }).ToDecimal();
decimal stc;
if (stHigh - stLow != 0)
stc = (st - stLow) / (stHigh - stLow) * 200m - 100m;
else
stc = _stcPrev;
if (_stcPass)
stc = Factor * (stc - _stcPrev) + _stcPrev;
_stcPrev = stc;
_stcPass = true;
return new DecimalIndicatorValue(this, stc, t) { IsFinal = input.IsFinal };
}
public override void Reset() {
_fast?.Reset();
_slow?.Reset();
_macdHigh?.Reset();
_macdLow?.Reset();
_stHigh?.Reset();
_stLow?.Reset();
_stPrev = 0m;
_stcPrev = 0m;
_stPass = false;
_stcPass = false;
_inited = false;
base.Reset();
}
}
/// <summary>
/// TRIX oscillator implementation.
/// </summary>
private sealed class Trix : BaseIndicator {
private readonly ExponentialMovingAverage _ema1;
private readonly ExponentialMovingAverage _ema2;
private readonly ExponentialMovingAverage _ema3;
private decimal? _prev;
public Trix(int length)
{
_ema1 = new ExponentialMovingAverage { Length = length };
_ema2 = new ExponentialMovingAverage { Length = length };
_ema3 = new ExponentialMovingAverage { Length = length };
}
public override int NumValuesToInitialize => 1;
protected override bool CalcIsFormed() => _ema3.IsFormed && _prev != null;
protected override IIndicatorValue OnProcess(IIndicatorValue input) {
var ema1 = _ema1.Process(input);
var ema2 = _ema2.Process(ema1);
var ema3 = _ema3.Process(ema2);
var value = ema3.ToDecimal();
decimal trix = 0m;
if (_prev != null && _prev != 0)
trix = (value - _prev.Value) / _prev.Value;
_prev = value;
return new DecimalIndicatorValue(this, trix, input.Time) { IsFinal = input.IsFinal };
}
public override void Reset() {
_ema1.Reset();
_ema2.Reset();
_ema3.Reset();
_prev = null;
base.Reset();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class color_schaff_trix_trend_cycle_strategy(Strategy):
def __init__(self):
super(color_schaff_trix_trend_cycle_strategy, self).__init__()
self._fast_trix_length = self.Param("FastTrixLength", 5) \
.SetDisplay("Fast TRIX", "Fast TRIX length", "Indicator")
self._slow_trix_length = self.Param("SlowTrixLength", 12) \
.SetDisplay("Slow TRIX", "Slow TRIX length", "Indicator")
self._cycle = self.Param("Cycle", 10) \
.SetDisplay("Cycle", "Cycle length", "Indicator")
self._high_level = self.Param("HighLevel", 20) \
.SetDisplay("High Level", "Upper threshold", "Indicator")
self._low_level = self.Param("LowLevel", -20) \
.SetDisplay("Low Level", "Lower threshold", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for candles", "General")
self._stop_loss = self.Param("StopLoss", 1.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit = self.Param("TakeProfit", 2.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._buy_open = self.Param("BuyOpen", True) \
.SetDisplay("Buy Open", "Allow buy entries", "Trading")
self._sell_open = self.Param("SellOpen", True) \
.SetDisplay("Sell Open", "Allow sell entries", "Trading")
self._buy_close = self.Param("BuyClose", True) \
.SetDisplay("Buy Close", "Allow closing long", "Trading")
self._sell_close = self.Param("SellClose", True) \
.SetDisplay("Sell Close", "Allow closing short", "Trading")
self._factor = self.Param("Factor", 0.5) \
.SetDisplay("Factor", "Smoothing factor for STC calculations", "Indicator")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 8) \
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Trading")
self._prev_stc_val = None
self._cooldown_remaining = 0
# TRIX internals (fast)
self._fast_ema1 = None
self._fast_ema2 = None
self._fast_ema3 = None
self._fast_prev = None
# TRIX internals (slow)
self._slow_ema1 = None
self._slow_ema2 = None
self._slow_ema3 = None
self._slow_prev = None
# STC internals
self._macd_high = None
self._macd_low = None
self._st_high = None
self._st_low = None
self._st_prev = 0.0
self._stc_prev = 0.0
self._st_pass = False
self._stc_pass = False
@property
def FastTrixLength(self):
return self._fast_trix_length.Value
@FastTrixLength.setter
def FastTrixLength(self, value):
self._fast_trix_length.Value = value
@property
def SlowTrixLength(self):
return self._slow_trix_length.Value
@SlowTrixLength.setter
def SlowTrixLength(self, value):
self._slow_trix_length.Value = value
@property
def Cycle(self):
return self._cycle.Value
@Cycle.setter
def Cycle(self, value):
self._cycle.Value = value
@property
def HighLevel(self):
return self._high_level.Value
@HighLevel.setter
def HighLevel(self, value):
self._high_level.Value = value
@property
def LowLevel(self):
return self._low_level.Value
@LowLevel.setter
def LowLevel(self, value):
self._low_level.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.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 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 Factor(self):
return self._factor.Value
@Factor.setter
def Factor(self, value):
self._factor.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(color_schaff_trix_trend_cycle_strategy, self).OnStarted2(time)
fast_len = self.FastTrixLength
slow_len = self.SlowTrixLength
cyc = self.Cycle
self._fast_ema1 = ExponentialMovingAverage()
self._fast_ema1.Length = fast_len
self._fast_ema2 = ExponentialMovingAverage()
self._fast_ema2.Length = fast_len
self._fast_ema3 = ExponentialMovingAverage()
self._fast_ema3.Length = fast_len
self._fast_prev = None
self._slow_ema1 = ExponentialMovingAverage()
self._slow_ema1.Length = slow_len
self._slow_ema2 = ExponentialMovingAverage()
self._slow_ema2.Length = slow_len
self._slow_ema3 = ExponentialMovingAverage()
self._slow_ema3.Length = slow_len
self._slow_prev = None
self._macd_high = Highest()
self._macd_high.Length = cyc
self._macd_low = Lowest()
self._macd_low.Length = cyc
self._st_high = Highest()
self._st_high.Length = cyc
self._st_low = Lowest()
self._st_low.Length = cyc
self._st_prev = 0.0
self._stc_prev = 0.0
self._st_pass = False
self._stc_pass = False
self._prev_stc_val = None
self._cooldown_remaining = 0
self.SubscribeCandles(self.CandleType) \
.Bind(self.ProcessCandle) \
.Start()
self.StartProtection(
stopLoss=Unit(self.StopLoss, UnitTypes.Percent),
takeProfit=Unit(self.TakeProfit, UnitTypes.Percent)
)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
close = float(candle.ClosePrice)
t = candle.OpenTime
factor = float(self.Factor)
# Calculate fast TRIX
e1 = process_float(self._fast_ema1, close, t, True)
e2 = self._fast_ema2.Process(e1)
e3 = self._fast_ema3.Process(e2)
fast_val = float(e3)
fast_trix = 0.0
if self._fast_prev is not None and self._fast_prev != 0:
fast_trix = (fast_val - self._fast_prev) / self._fast_prev
self._fast_prev = fast_val
# Calculate slow TRIX
s1 = process_float(self._slow_ema1, close, t, True)
s2 = self._slow_ema2.Process(s1)
s3 = self._slow_ema3.Process(s2)
slow_val = float(s3)
slow_trix = 0.0
if self._slow_prev is not None and self._slow_prev != 0:
slow_trix = (slow_val - self._slow_prev) / self._slow_prev
self._slow_prev = slow_val
macd = fast_trix - slow_trix
# STC calculation
mh_result = process_float(self._macd_high, macd, t, True)
ml_result = process_float(self._macd_low, macd, t, True)
macd_high = float(mh_result)
macd_low = float(ml_result)
if macd_high - macd_low != 0:
st = (macd - macd_low) / (macd_high - macd_low) * 100.0
else:
st = self._st_prev
if self._st_pass:
st = factor * (st - self._st_prev) + self._st_prev
self._st_prev = st
self._st_pass = True
sh_result = process_float(self._st_high, st, t, True)
sl_result = process_float(self._st_low, st, t, True)
st_high = float(sh_result)
st_low = float(sl_result)
if st_high - st_low != 0:
stc = (st - st_low) / (st_high - st_low) * 200.0 - 100.0
else:
stc = self._stc_prev
if self._stc_pass:
stc = factor * (stc - self._stc_prev) + self._stc_prev
self._stc_prev = stc
self._stc_pass = True
if self._prev_stc_val is None:
self._prev_stc_val = stc
return
prev = self._prev_stc_val
high = float(self.HighLevel)
low = float(self.LowLevel)
crossed_up = stc > high and prev <= high
crossed_down = stc < low and prev >= low
long_exit = self.BuyClose and self.Position > 0 and stc < 0
short_exit = self.SellClose and self.Position < 0 and stc > 0
if long_exit:
self.SellMarket(self.Position)
self._cooldown_remaining = self.SignalCooldownBars
elif short_exit:
self.BuyMarket(abs(self.Position))
self._cooldown_remaining = self.SignalCooldownBars
elif self._cooldown_remaining == 0 and crossed_up and self.BuyOpen and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif self._cooldown_remaining == 0 and crossed_down and self.SellOpen and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.SignalCooldownBars
self._prev_stc_val = stc
def OnReseted(self):
super(color_schaff_trix_trend_cycle_strategy, self).OnReseted()
self._prev_stc_val = None
self._cooldown_remaining = 0
def CreateClone(self):
return color_schaff_trix_trend_cycle_strategy()