J-Lines リボン 4サイクルエンジン戦略
J-Lines Ribbon 4-Cycle Engine戦略は、EMAのリボンとAverage Directional Indexを使用して市場をCHOP、LONG、SHORTサイクルに分類します。エントリーは新しいサイクルの検出とキーEMAからのリバウンドで発生し、エグジットは逆方向のクロスまたはスイングブレイクでトリガーされます。
詳細
- エントリー条件:
- ロング: 新しいLONGサイクル、またはEMA72がEMA89より上にある状態でEMA72/EMA126の上でリバウンド。
- ショート: 新しいSHORTサイクル、またはEMA72がEMA89より下にある状態でEMA72/EMA126の下でリバウンド。
- ストップ: 直近のスイング高値/安値。
- デフォルト値:
DmiLength= 8AdxFloor= 12
- フィルター:
- カテゴリ: トレンド
- 方向: 両方
- インジケーター: EMA, ADX
- ストップ: はい
- 複雑さ: 中程度
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// J-Lines Ribbon 4-Cycle Engine strategy.
/// The market is CHOP while ADX is below AdxFloor, LONG when EMA72 is above EMA89 and price closes above EMA126, and SHORT in the mirror case.
/// A long opens on a new LONG cycle or when price dips to EMA72/EMA126 and closes back above it while EMA72 is above EMA89; shorts mirror this,
/// reversing an opposite position. The stop is the last swing low (high) and a position also closes when EMA72 crosses EMA89 against it.
/// </summary>
public class JLinesRibbon4CycleEngineStrategy : Strategy
{
private enum Cycles
{
Chop,
Long,
Short,
}
private readonly StrategyParam<int> _dmiLength;
private readonly StrategyParam<decimal> _adxFloor;
private readonly StrategyParam<int> _swingLength;
private readonly StrategyParam<DataType> _candleType;
private Highest _swingHigh;
private Lowest _swingLow;
private Cycles _prevCycle;
private decimal? _prevEma72;
private decimal? _prevEma89;
private decimal? _prevSwingHigh;
private decimal? _prevSwingLow;
private decimal? _stopPrice;
/// <summary>
/// Period of the DMI/ADX.
/// </summary>
public int DmiLength
{
get => _dmiLength.Value;
set => _dmiLength.Value = value;
}
/// <summary>
/// ADX level below which the market is CHOP.
/// </summary>
public decimal AdxFloor
{
get => _adxFloor.Value;
set => _adxFloor.Value = value;
}
/// <summary>
/// Candles the swing high/low spans.
/// </summary>
public int SwingLength
{
get => _swingLength.Value;
set => _swingLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="JLinesRibbon4CycleEngineStrategy"/>.
/// </summary>
public JLinesRibbon4CycleEngineStrategy()
{
_dmiLength = Param(nameof(DmiLength), 8)
.SetGreaterThanZero()
.SetDisplay("DMI Length", "Period of the DMI/ADX", "Indicators");
_adxFloor = Param(nameof(AdxFloor), 12m)
.SetNotNegative()
.SetDisplay("ADX Floor", "ADX level below which the market is CHOP", "Indicators");
_swingLength = Param(nameof(SwingLength), 10)
.SetGreaterThanZero()
.SetDisplay("Swing Length", "Candles the swing high/low spans", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_prevCycle = Cycles.Chop;
_prevEma72 = _prevEma89 = null;
_prevSwingHigh = _prevSwingLow = null;
_stopPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var ema72 = new ExponentialMovingAverage { Length = 72 };
var ema89 = new ExponentialMovingAverage { Length = 89 };
var ema126 = new ExponentialMovingAverage { Length = 126 };
var adx = new AverageDirectionalIndex { Length = DmiLength };
_swingHigh = new Highest { Length = SwingLength };
_swingLow = new Lowest { Length = SwingLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema72, ema89, ema126, adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema72);
DrawIndicator(area, ema89);
DrawIndicator(area, ema126);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, adx);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue ema72Value, IIndicatorValue ema89Value, IIndicatorValue ema126Value, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
// The swing is measured on the candles before this one.
var swingHigh = _prevSwingHigh;
var swingLow = _prevSwingLow;
var highValue = _swingHigh.Process(new DecimalIndicatorValue(_swingHigh, candle.HighPrice, candle.OpenTime) { IsFinal = true });
var lowValue = _swingLow.Process(new DecimalIndicatorValue(_swingLow, candle.LowPrice, candle.OpenTime) { IsFinal = true });
_prevSwingHigh = _swingHigh.IsFormed ? highValue.GetValue<decimal>() : null;
_prevSwingLow = _swingLow.IsFormed ? lowValue.GetValue<decimal>() : null;
if (!ema72Value.IsFormed || !ema89Value.IsFormed || !ema126Value.IsFormed || !adxValue.IsFormed
|| adxValue is not AverageDirectionalIndexValue { MovingAverage: decimal adx })
return;
var ema72 = ema72Value.GetValue<decimal>();
var ema89 = ema89Value.GetValue<decimal>();
var ema126 = ema126Value.GetValue<decimal>();
var close = candle.ClosePrice;
var cycle = adx < AdxFloor
? Cycles.Chop
: ema72 > ema89 && close > ema126
? Cycles.Long
: ema72 < ema89 && close < ema126
? Cycles.Short
: Cycles.Chop;
var prevCycle = _prevCycle;
var prevEma72 = _prevEma72;
var prevEma89 = _prevEma89;
_prevCycle = cycle;
_prevEma72 = ema72;
_prevEma89 = ema89;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var crossDown = prevEma72 is decimal p72 && prevEma89 is decimal p89 && p72 >= p89 && ema72 < ema89;
var crossUp = prevEma72 is decimal q72 && prevEma89 is decimal q89 && q72 <= q89 && ema72 > ema89;
if (Position > 0 && (crossDown || _stopPrice is decimal longStop && candle.LowPrice <= longStop))
{
SellMarket(Position);
_stopPrice = null;
return;
}
if (Position < 0 && (crossUp || _stopPrice is decimal shortStop && candle.HighPrice >= shortStop))
{
BuyMarket(-Position);
_stopPrice = null;
return;
}
var reboundUp = (candle.LowPrice <= ema72 && close > ema72) || (candle.LowPrice <= ema126 && close > ema126);
var reboundDown = (candle.HighPrice >= ema72 && close < ema72) || (candle.HighPrice >= ema126 && close < ema126);
var longSignal = (cycle == Cycles.Long && prevCycle != Cycles.Long) || (ema72 > ema89 && reboundUp);
var shortSignal = (cycle == Cycles.Short && prevCycle != Cycles.Short) || (ema72 < ema89 && reboundDown);
if (longSignal && Position <= 0 && swingLow is decimal stopLow && stopLow < close)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = stopLow;
}
else if (shortSignal && Position >= 0 && swingHigh is decimal stopHigh && stopHigh > close)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = stopHigh;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageDirectionalIndex, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
CYCLE_CHOP = 0
CYCLE_LONG = 1
CYCLE_SHORT = 2
class j_lines_ribbon_4_cycle_engine_strategy(Strategy):
"""
J-Lines Ribbon 4-Cycle Engine strategy.
The market is CHOP while ADX is below AdxFloor, LONG when EMA72 is above EMA89 and price closes above EMA126, and SHORT in the mirror case.
A long opens on a new LONG cycle or when price dips to EMA72/EMA126 and closes back above it while EMA72 is above EMA89; shorts mirror this,
reversing an opposite position. The stop is the last swing low (high) and a position also closes when EMA72 crosses EMA89 against it.
"""
def __init__(self):
super(j_lines_ribbon_4_cycle_engine_strategy, self).__init__()
self._dmi_length = self.Param("DmiLength", 8).SetGreaterThanZero().SetDisplay("DMI Length", "Period of the DMI/ADX", "Indicators")
self._adx_floor = self.Param("AdxFloor", 12.0).SetNotNegative().SetDisplay("ADX Floor", "ADX level below which the market is CHOP", "Indicators")
self._swing_length = self.Param("SwingLength", 10).SetGreaterThanZero().SetDisplay("Swing Length", "Candles the swing high/low spans", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._swing_high = None
self._swing_low = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_cycle = CYCLE_CHOP
self._prev_ema72 = None
self._prev_ema89 = None
self._prev_swing_high = None
self._prev_swing_low = None
self._stop_price = None
def OnReseted(self):
super(j_lines_ribbon_4_cycle_engine_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(j_lines_ribbon_4_cycle_engine_strategy, self).OnStarted2(time)
self._reset_state()
ema72 = ExponentialMovingAverage()
ema72.Length = 72
ema89 = ExponentialMovingAverage()
ema89.Length = 89
ema126 = ExponentialMovingAverage()
ema126.Length = 126
adx = AverageDirectionalIndex()
adx.Length = self._dmi_length.Value
self._swing_high = Highest()
self._swing_high.Length = self._swing_length.Value
self._swing_low = Lowest()
self._swing_low.Length = self._swing_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema72, ema89, ema126, adx, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema72)
self.DrawIndicator(area, ema89)
self.DrawIndicator(area, ema126)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _process_candle(self, candle, ema72_value, ema89_value, ema126_value, adx_value):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
time = candle.OpenTime
# The swing is measured on the candles before this one.
swing_high = self._prev_swing_high
swing_low = self._prev_swing_low
high_value = float(process_float(self._swing_high, high, time, True))
low_value = float(process_float(self._swing_low, low, time, True))
self._prev_swing_high = high_value if self._swing_high.IsFormed else None
self._prev_swing_low = low_value if self._swing_low.IsFormed else None
if not ema72_value.IsFormed or not ema89_value.IsFormed or not ema126_value.IsFormed or not adx_value.IsFormed:
return
if adx_value.MovingAverage is None:
return
adx = float(adx_value.MovingAverage)
ema72 = float(ema72_value.GetValue[Decimal](None))
ema89 = float(ema89_value.GetValue[Decimal](None))
ema126 = float(ema126_value.GetValue[Decimal](None))
if adx < float(self._adx_floor.Value):
cycle = CYCLE_CHOP
elif ema72 > ema89 and close > ema126:
cycle = CYCLE_LONG
elif ema72 < ema89 and close < ema126:
cycle = CYCLE_SHORT
else:
cycle = CYCLE_CHOP
prev_cycle = self._prev_cycle
prev_ema72 = self._prev_ema72
prev_ema89 = self._prev_ema89
self._prev_cycle = cycle
self._prev_ema72 = ema72
self._prev_ema89 = ema89
if not self.IsFormedAndOnlineAndAllowTrading():
return
has_prev = prev_ema72 is not None and prev_ema89 is not None
cross_down = has_prev and prev_ema72 >= prev_ema89 and ema72 < ema89
cross_up = has_prev and prev_ema72 <= prev_ema89 and ema72 > ema89
if self.Position > 0 and (cross_down or (self._stop_price is not None and low <= self._stop_price)):
self.SellMarket(self.Position)
self._stop_price = None
return
if self.Position < 0 and (cross_up or (self._stop_price is not None and high >= self._stop_price)):
self.BuyMarket(-self.Position)
self._stop_price = None
return
rebound_up = (low <= ema72 and close > ema72) or (low <= ema126 and close > ema126)
rebound_down = (high >= ema72 and close < ema72) or (high >= ema126 and close < ema126)
long_signal = (cycle == CYCLE_LONG and prev_cycle != CYCLE_LONG) or (ema72 > ema89 and rebound_up)
short_signal = (cycle == CYCLE_SHORT and prev_cycle != CYCLE_SHORT) or (ema72 < ema89 and rebound_down)
if long_signal and self.Position <= 0 and swing_low is not None and swing_low < close:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = swing_low
elif short_signal and self.Position >= 0 and swing_high is not None and swing_high > close:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = swing_high
def CreateClone(self):
return j_lines_ribbon_4_cycle_engine_strategy()