Escort トレンド戦略
Escort トレンド戦略は、高速・低速の加重移動平均(WMA)とMACD・CCIの確認を組み合わせます。高速WMAが低速WMAより上にあり、MACDメインラインがシグナルラインを上抜け、CCIが正のしきい値を超えるとロングポジションが開かれます。ショートポジションは逆の条件でトリガーされます。この戦略はオプションで固定のストップロス、テイクプロフィット、トレーリングストップを使用します。
詳細
- エントリー条件:
- ロング:
FastWMA > SlowWMAかつMACD > SignalかつCCI > +Threshold。 - ショート:
FastWMA < SlowWMAかつMACD < SignalかつCCI < -Threshold。
- ロング:
- ロング/ショート: 両方向。
- エグジット条件:
- 反対のエントリーシグナル。
- オプションのストップロス、テイクプロフィット、またはトレーリングストップ。
- ストップ: はい、ユーザー定義。
- デフォルト値:
Fast WMA= 8Slow WMA= 18CCI Period= 14CCI Threshold= 100MACD Fast EMA= 8MACD Slow EMA= 18Take Profit= 200Stop Loss= 55Trailing Stop= 35Trailing Step= 3
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: 両方
- インジケーター: 複数
- ストップ: はい
- 複雑さ: 中程度
- 時間軸: 短期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// WMA trend confirmation, MACD/signal crossover and CCI filter.
/// Fixed protection uses native actual-fill orders; trailing is evaluated
/// conservatively on completed-candle closes with a configurable step.
/// </summary>
public class EscortTrendStrategy : Strategy
{
private readonly StrategyParam<int> _fastWmaPeriod, _slowWmaPeriod, _cciPeriod;
private readonly StrategyParam<decimal> _cciThreshold;
private readonly StrategyParam<int> _macdFastEma, _macdSlowEma, _macdSignalPeriod;
private readonly StrategyParam<decimal> _stopLoss, _takeProfit, _trailingStop, _trailingStep;
private readonly StrategyParam<DataType> _candleType;
private decimal? _macdFast, _macdSlow, _signal, _previousDifference;
private decimal? _entry, _favorable, _trailingLevel;
private decimal _trackedPosition;
private int _samples;
private Order _pending;
public int FastWmaPeriod { get => _fastWmaPeriod.Value; set => _fastWmaPeriod.Value = value; }
public int SlowWmaPeriod { get => _slowWmaPeriod.Value; set => _slowWmaPeriod.Value = value; }
public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
public decimal CciThreshold { get => _cciThreshold.Value; set => _cciThreshold.Value = value; }
public int MacdFastEma { get => _macdFastEma.Value; set => _macdFastEma.Value = value; }
public int MacdSlowEma { get => _macdSlowEma.Value; set => _macdSlowEma.Value = value; }
public int MacdSignalPeriod { get => _macdSignalPeriod.Value; set => _macdSignalPeriod.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal TrailingStop { get => _trailingStop.Value; set => _trailingStop.Value = value; }
public decimal TrailingStep { get => _trailingStep.Value; set => _trailingStep.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public EscortTrendStrategy()
{
_fastWmaPeriod = Param(nameof(FastWmaPeriod), 8).SetGreaterThanZero()
.SetDisplay("Fast WMA", "Fast weighted moving average", "Indicator");
_slowWmaPeriod = Param(nameof(SlowWmaPeriod), 18).SetGreaterThanZero()
.SetDisplay("Slow WMA", "Slow weighted moving average", "Indicator");
_cciPeriod = Param(nameof(CciPeriod), 14).SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI calculation period", "Indicator");
_cciThreshold = Param(nameof(CciThreshold), 100m).SetNotNegative()
.SetDisplay("CCI Threshold", "Long and short confirmation level", "Indicator");
_macdFastEma = Param(nameof(MacdFastEma), 8).SetGreaterThanZero()
.SetDisplay("MACD Fast EMA", "Fast EMA of MACD", "Indicator");
_macdSlowEma = Param(nameof(MacdSlowEma), 18).SetGreaterThanZero()
.SetDisplay("MACD Slow EMA", "Slow EMA of MACD", "Indicator");
_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9).SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal-line EMA length", "Indicator");
_stopLoss = Param(nameof(StopLoss), 55m).SetNotNegative()
.SetDisplay("Stop Loss", "Absolute distance from filled entry", "Risk");
_takeProfit = Param(nameof(TakeProfit), 200m).SetNotNegative()
.SetDisplay("Take Profit", "Absolute distance from filled entry", "Risk");
_trailingStop = Param(nameof(TrailingStop), 35m).SetNotNegative()
.SetDisplay("Trailing Stop", "Optional close-based trailing distance", "Risk");
_trailingStep = Param(nameof(TrailingStep), 3m).SetNotNegative()
.SetDisplay("Trailing Step", "Minimum favorable increment per trail update", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Signal timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
private void ResetHistory()
{
_macdFast = _macdSlow = _signal = _previousDifference = null;
_entry = _favorable = _trailingLevel = null;
_trackedPosition = 0m;
_samples = 0;
_pending = null;
}
protected override void OnReseted()
{
base.OnReseted();
ResetHistory();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetHistory();
StartProtection(
takeProfit: new Unit(TakeProfit, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss, UnitTypes.Absolute),
useMarketOrders: true);
var fastWma = new WeightedMovingAverage { Length = FastWmaPeriod };
var slowWma = new WeightedMovingAverage { Length = SlowWmaPeriod };
var cci = new CommodityChannelIndex { Length = CciPeriod };
SubscribeCandles(CandleType).Bind(fastWma, slowWma, cci, ProcessCandle).Start();
}
protected override void OnNewPosition(Position position)
{
base.OnNewPosition(position);
ObservePosition(position);
}
protected override void OnPositionChanged(Position position)
{
base.OnPositionChanged(position);
ObservePosition(position);
}
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
ObservePosition(position);
}
private void ObservePosition(Position position)
{
if (position.Security != Security || position.Portfolio != Portfolio)
return;
var size = position.CurrentValue ?? 0m;
if (size == 0m)
{
_entry = _favorable = _trailingLevel = null;
_trackedPosition = 0m;
return;
}
if (position.AveragePrice is not decimal average || average <= 0m)
return;
if (_entry == average && _trackedPosition * size > 0m)
{
_trackedPosition = size;
return;
}
_entry = _favorable = average;
_trailingLevel = null;
_trackedPosition = size;
}
private static decimal Ema(decimal? old, decimal value, int period)
=> old is decimal prior ? prior + 2m / (period + 1m) * (value - prior) : value;
private void ProcessCandle(ICandleMessage candle, decimal fastWma, decimal slowWma, decimal cci)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
_macdFast = Ema(_macdFast, close, MacdFastEma);
_macdSlow = Ema(_macdSlow, close, MacdSlowEma);
var macd = _macdFast.Value - _macdSlow.Value;
_signal = Ema(_signal, macd, MacdSignalPeriod);
var difference = macd - _signal.Value;
_samples++;
var crossUp = _previousDifference is decimal previous && previous <= 0m && difference > 0m;
var crossDown = _previousDifference is decimal previousDown && previousDown >= 0m && difference < 0m;
_previousDifference = difference;
if (_samples < Math.Max(Math.Max(FastWmaPeriod, SlowWmaPeriod),
Math.Max(CciPeriod, MacdSlowEma + MacdSignalPeriod)))
return;
if (!IsFormedAndOnlineAndAllowTrading()
|| (_pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed)))
return;
if (Position != 0m && TrailingStop > 0m && _entry is decimal entry && _favorable is decimal favorable)
{
var longSide = Position > 0m;
var advance = longSide ? close - favorable : favorable - close;
if (advance >= TrailingStep)
{
_favorable = close;
favorable = close;
}
if (_trailingLevel is null && (longSide ? close - entry : entry - close) >= TrailingStop)
_trailingLevel = longSide ? close - TrailingStop : close + TrailingStop;
else if (_trailingLevel is not null && advance >= TrailingStep)
{
var candidate = longSide ? close - TrailingStop : close + TrailingStop;
_trailingLevel = longSide
? Math.Max(_trailingLevel.Value, candidate)
: Math.Min(_trailingLevel.Value, candidate);
}
if (_trailingLevel is decimal trail && (longSide ? close <= trail : close >= trail))
{
_pending = longSide ? SellMarket(Position) : BuyMarket(-Position);
return;
}
}
var buy = fastWma > slowWma && crossUp && cci > CciThreshold;
var sell = fastWma < slowWma && crossDown && cci < -CciThreshold;
if (buy)
{
if (Position < 0m)
_pending = BuyMarket(-Position + Volume);
else if (Position == 0m)
_pending = BuyMarket(Volume);
}
else if (sell)
{
if (Position > 0m)
_pending = SellMarket(Position + Volume);
else if (Position == 0m)
_pending = SellMarket(Volume);
}
}
}
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, OrderStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import WeightedMovingAverage, CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class escort_trend_strategy(Strategy):
"""WMA and CCI-confirmed MACD crossover with native risk and close-based trailing."""
def __init__(self):
super(escort_trend_strategy, self).__init__()
self._fast_wma_period = self.Param("FastWmaPeriod", 8).SetGreaterThanZero().SetDisplay("Fast WMA", "Fast WMA", "Indicator")
self._slow_wma_period = self.Param("SlowWmaPeriod", 18).SetGreaterThanZero().SetDisplay("Slow WMA", "Slow WMA", "Indicator")
self._cci_period = self.Param("CciPeriod", 14).SetGreaterThanZero().SetDisplay("CCI Period", "CCI period", "Indicator")
self._cci_threshold = self.Param("CciThreshold", 100.0).SetNotNegative().SetDisplay("CCI Threshold", "CCI confirmation", "Indicator")
self._macd_fast = self.Param("MacdFastEma", 8).SetGreaterThanZero().SetDisplay("MACD Fast EMA", "MACD fast", "Indicator")
self._macd_slow = self.Param("MacdSlowEma", 18).SetGreaterThanZero().SetDisplay("MACD Slow EMA", "MACD slow", "Indicator")
self._macd_signal = self.Param("MacdSignalPeriod", 9).SetGreaterThanZero().SetDisplay("MACD Signal", "MACD signal EMA", "Indicator")
self._stop_loss = self.Param("StopLoss", 55.0).SetNotNegative().SetDisplay("Stop Loss", "Absolute stop distance", "Risk")
self._take_profit = self.Param("TakeProfit", 200.0).SetNotNegative().SetDisplay("Take Profit", "Absolute target distance", "Risk")
self._trailing_stop = self.Param("TrailingStop", 35.0).SetNotNegative().SetDisplay("Trailing Stop", "Close-based trailing distance", "Risk")
self._trailing_step = self.Param("TrailingStep", 3.0).SetNotNegative().SetDisplay("Trailing Step", "Trail adjustment increment", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Signal candles", "General")
self._reset_history()
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _reset_history(self):
self._macd_fast_value = self._macd_slow_value = self._signal_value = self._previous_difference = None
self._entry = self._favorable = self._trailing_level = None
self._tracked_position = 0.0
self._samples = 0
self._pending = None
def OnReseted(self):
super(escort_trend_strategy, self).OnReseted()
self._reset_history()
def OnStarted2(self, time):
super(escort_trend_strategy, self).OnStarted2(time)
self._reset_history()
self.StartProtection(takeProfit=Unit(self._take_profit.Value, UnitTypes.Absolute),
stopLoss=Unit(self._stop_loss.Value, UnitTypes.Absolute),
useMarketOrders=True)
fast = WeightedMovingAverage()
fast.Length = int(self._fast_wma_period.Value)
slow = WeightedMovingAverage()
slow.Length = int(self._slow_wma_period.Value)
cci = CommodityChannelIndex()
cci.Length = int(self._cci_period.Value)
self.SubscribeCandles(self.CandleType).Bind(fast, slow, cci, self.ProcessCandle).Start()
def OnNewPosition(self, position):
super(escort_trend_strategy, self).OnNewPosition(position)
self._observe_position(position)
def OnPositionChanged(self, position):
super(escort_trend_strategy, self).OnPositionChanged(position)
self._observe_position(position)
def OnPositionReceived(self, position):
super(escort_trend_strategy, self).OnPositionReceived(position)
self._observe_position(position)
def _observe_position(self, position):
if position.Security != self.Security or position.Portfolio != self.Portfolio:
return
size = float(position.CurrentValue or 0)
if size == 0:
self._entry = self._favorable = self._trailing_level = None
self._tracked_position = 0.0
return
if position.AveragePrice is None or position.AveragePrice <= 0:
return
average = float(position.AveragePrice)
if self._entry == average and self._tracked_position * size > 0:
self._tracked_position = size
return
self._entry = self._favorable = average
self._trailing_level = None
self._tracked_position = size
@staticmethod
def _ema(previous, value, period):
return value if previous is None else previous + 2.0 / (period + 1.0) * (value - previous)
def ProcessCandle(self, candle, fast_wma, slow_wma, cci):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
self._macd_fast_value = self._ema(self._macd_fast_value, close, int(self._macd_fast.Value))
self._macd_slow_value = self._ema(self._macd_slow_value, close, int(self._macd_slow.Value))
macd = self._macd_fast_value - self._macd_slow_value
self._signal_value = self._ema(self._signal_value, macd, int(self._macd_signal.Value))
difference = macd - self._signal_value
self._samples += 1
cross_up = self._previous_difference is not None and self._previous_difference <= 0 and difference > 0
cross_down = self._previous_difference is not None and self._previous_difference >= 0 and difference < 0
self._previous_difference = difference
required = max(int(self._fast_wma_period.Value), int(self._slow_wma_period.Value),
int(self._cci_period.Value), int(self._macd_slow.Value) + int(self._macd_signal.Value))
if self._samples < required:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed):
return
position = float(self.Position)
distance = float(self._trailing_stop.Value)
step = float(self._trailing_step.Value)
if position != 0 and distance > 0 and self._entry is not None and self._favorable is not None:
side_long = position > 0
advance = close - self._favorable if side_long else self._favorable - close
if advance >= step:
self._favorable = close
if self._trailing_level is None and (close - self._entry if side_long else self._entry - close) >= distance:
self._trailing_level = close - distance if side_long else close + distance
elif self._trailing_level is not None and advance >= step:
candidate = close - distance if side_long else close + distance
self._trailing_level = max(self._trailing_level, candidate) if side_long else min(self._trailing_level, candidate)
if self._trailing_level is not None and (close <= self._trailing_level if side_long else close >= self._trailing_level):
self._pending = self.SellMarket(self.Position) if side_long else self.BuyMarket(-self.Position)
return
buy = float(fast_wma) > float(slow_wma) and cross_up and float(cci) > float(self._cci_threshold.Value)
sell = float(fast_wma) < float(slow_wma) and cross_down and float(cci) < -float(self._cci_threshold.Value)
if buy:
if position < 0:
self._pending = self.BuyMarket(-self.Position + self.Volume)
elif position == 0:
self._pending = self.BuyMarket(self.Volume)
elif sell:
if position > 0:
self._pending = self.SellMarket(self.Position + self.Volume)
elif position == 0:
self._pending = self.SellMarket(self.Volume)
def CreateClone(self):
return escort_trend_strategy()