Hurst Future Lines of Demarcation戦略
この戦略は、平滑化されたFLD(Future Line of Demarcation)と3つのサイクル長(シグナル、トレード、トレンド)を使用します。特定のトレンド状態でシグナルFLDを価格がクロスした際にエントリーし、選択された値のクロスでエグジットします。
詳細
- エントリー条件:
- トレンド状態が1の間に価格がシグナルFLDを上抜けたときに買い。
- トレンド状態が6の間に価格がシグナルFLDを下抜けたときに売り。
- ロング/ショート: 両方。
- エグジット条件: トレードの反対方向に
CloseTrigger1がCloseTrigger2をクロスしたときにポジションを閉じる。 - ストップ: なし。
- デフォルト値:
SmoothFld= falseFldSmoothing= 5SignalCycleLength= 5TradeCycleLength= 20TrendCycleLength= 80CloseTrigger1= PriceCloseTrigger2= Trade
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public enum HurstFldTrigger
{
Price,
Signal,
Trade,
Trend,
}
/// <summary>
/// Hurst Future Lines of Demarcation strategy.
/// FLDs are represented by cycle-based displaced price lines; price crosses the signal
/// line only trade when the longer-cycle state agrees.
/// </summary>
public class HurstFutureLinesOfDemarcationStrategy : Strategy
{
private readonly StrategyParam<bool> _smoothFld;
private readonly StrategyParam<int> _fldSmoothing;
private readonly StrategyParam<int> _signalCycleLength;
private readonly StrategyParam<int> _tradeCycleLength;
private readonly StrategyParam<int> _trendCycleLength;
private readonly StrategyParam<HurstFldTrigger> _closeTrigger1;
private readonly StrategyParam<HurstFldTrigger> _closeTrigger2;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closes = [];
private decimal? _prevPrice;
private decimal? _prevSignal;
private decimal? _prevTrade;
private decimal? _prevTrend;
public bool SmoothFld { get => _smoothFld.Value; set => _smoothFld.Value = value; }
public int FldSmoothing { get => _fldSmoothing.Value; set => _fldSmoothing.Value = value; }
public int SignalCycleLength { get => _signalCycleLength.Value; set => _signalCycleLength.Value = value; }
public int TradeCycleLength { get => _tradeCycleLength.Value; set => _tradeCycleLength.Value = value; }
public int TrendCycleLength { get => _trendCycleLength.Value; set => _trendCycleLength.Value = value; }
public HurstFldTrigger CloseTrigger1 { get => _closeTrigger1.Value; set => _closeTrigger1.Value = value; }
public HurstFldTrigger CloseTrigger2 { get => _closeTrigger2.Value; set => _closeTrigger2.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public HurstFutureLinesOfDemarcationStrategy()
{
_smoothFld = Param(nameof(SmoothFld), false)
.SetDisplay("Smooth FLD", "Smooth displaced FLD values.", "FLD");
_fldSmoothing = Param(nameof(FldSmoothing), 5)
.SetGreaterThanZero()
.SetDisplay("FLD Smoothing", "Number of displaced observations used for smoothing.", "FLD");
_signalCycleLength = Param(nameof(SignalCycleLength), 5)
.SetGreaterThanZero()
.SetDisplay("Signal Cycle", "Signal-cycle length.", "Cycles");
_tradeCycleLength = Param(nameof(TradeCycleLength), 20)
.SetGreaterThanZero()
.SetDisplay("Trade Cycle", "Trade-cycle length.", "Cycles");
_trendCycleLength = Param(nameof(TrendCycleLength), 80)
.SetGreaterThanZero()
.SetDisplay("Trend Cycle", "Trend-cycle length.", "Cycles");
_closeTrigger1 = Param(nameof(CloseTrigger1), HurstFldTrigger.Price)
.SetDisplay("Close Trigger 1", "First line used by the exit crossover.", "Exit");
_closeTrigger2 = Param(nameof(CloseTrigger2), HurstFldTrigger.Trade)
.SetDisplay("Close Trigger 2", "Second line used by the exit crossover.", "Exit");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Candles used by Hurst FLDs.", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_closes.Clear();
_prevPrice = null;
_prevSignal = null;
_prevTrade = null;
_prevTrend = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_closes.Add(candle.ClosePrice);
var signal = GetFld(SignalCycleLength);
var trade = GetFld(TradeCycleLength);
var trend = GetFld(TrendCycleLength);
if (signal is null || trade is null || trend is null)
{
TrimHistory();
return;
}
var price = candle.ClosePrice;
var state = GetTrendState(price, trade.Value, trend.Value);
if (_prevPrice is decimal prevPrice &&
_prevSignal is decimal prevSignal &&
_prevTrade is decimal prevTrade &&
_prevTrend is decimal prevTrend)
{
var trigger1 = Resolve(CloseTrigger1, price, signal.Value, trade.Value, trend.Value);
var trigger2 = Resolve(CloseTrigger2, price, signal.Value, trade.Value, trend.Value);
var prevTrigger1 = Resolve(CloseTrigger1, prevPrice, prevSignal, prevTrade, prevTrend);
var prevTrigger2 = Resolve(CloseTrigger2, prevPrice, prevSignal, prevTrade, prevTrend);
var exitLong = Position > 0 && prevTrigger1 >= prevTrigger2 && trigger1 < trigger2;
var exitShort = Position < 0 && prevTrigger1 <= prevTrigger2 && trigger1 > trigger2;
if (exitLong)
SellMarket(Math.Abs(Position));
else if (exitShort)
BuyMarket(Math.Abs(Position));
else
{
var crossUp = prevPrice <= prevSignal && price > signal.Value;
var crossDown = prevPrice >= prevSignal && price < signal.Value;
if (Position == 0 && crossUp && state == 1)
BuyMarket();
else if (Position == 0 && crossDown && state == 6)
SellMarket();
}
}
_prevPrice = price;
_prevSignal = signal;
_prevTrade = trade;
_prevTrend = trend;
TrimHistory();
}
private int GetTrendState(decimal price, decimal tradeFld, decimal trendFld)
{
if (price > tradeFld && tradeFld > trendFld)
return 1;
if (price < tradeFld && tradeFld < trendFld)
return 6;
return 0;
}
private decimal? GetFld(int cycleLength)
{
var displacement = Math.Max(1, cycleLength / 2);
var target = _closes.Count - 1 - displacement;
if (target < 0)
return null;
if (!SmoothFld)
return _closes[target];
var from = Math.Max(0, target - FldSmoothing + 1);
var count = target - from + 1;
return _closes.Skip(from).Take(count).Average();
}
private static decimal Resolve(HurstFldTrigger trigger, decimal price, decimal signal, decimal trade, decimal trend)
=> trigger switch
{
HurstFldTrigger.Price => price,
HurstFldTrigger.Signal => signal,
HurstFldTrigger.Trade => trade,
HurstFldTrigger.Trend => trend,
_ => price,
};
private void TrimHistory()
{
var keep = Math.Max(TrendCycleLength, Math.Max(TradeCycleLength, SignalCycleLength)) + FldSmoothing + 4;
if (_closes.Count > keep)
_closes.RemoveRange(0, _closes.Count - keep);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class hurst_future_lines_of_demarcation_strategy(Strategy):
def __init__(self):
super(hurst_future_lines_of_demarcation_strategy, self).__init__()
self._smooth_fld = self.Param("SmoothFld", False)
self._fld_smoothing = self.Param("FldSmoothing", 5).SetGreaterThanZero()
self._signal_cycle = self.Param("SignalCycleLength", 5).SetGreaterThanZero()
self._trade_cycle = self.Param("TradeCycleLength", 20).SetGreaterThanZero()
self._trend_cycle = self.Param("TrendCycleLength", 80).SetGreaterThanZero()
self._close_trigger1 = self.Param("CloseTrigger1", "Price")
self._close_trigger2 = self.Param("CloseTrigger2", "Trade")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._closes = []
self._prev_price = None
self._prev_signal = None
self._prev_trade = None
self._prev_trend = None
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(hurst_future_lines_of_demarcation_strategy, self).OnReseted()
self._closes = []
self._prev_price = None
self._prev_signal = None
self._prev_trade = None
self._prev_trend = None
def OnStarted2(self, time):
super(hurst_future_lines_of_demarcation_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
self._closes.append(price)
signal = self._get_fld(int(self._signal_cycle.Value))
trade = self._get_fld(int(self._trade_cycle.Value))
trend = self._get_fld(int(self._trend_cycle.Value))
if signal is None or trade is None or trend is None:
self._trim()
return
state = self._trend_state(price, trade, trend)
if self._prev_price is not None:
trigger1 = self._resolve(str(self._close_trigger1.Value), price, signal, trade, trend)
trigger2 = self._resolve(str(self._close_trigger2.Value), price, signal, trade, trend)
prev_trigger1 = self._resolve(str(self._close_trigger1.Value), self._prev_price, self._prev_signal, self._prev_trade, self._prev_trend)
prev_trigger2 = self._resolve(str(self._close_trigger2.Value), self._prev_price, self._prev_signal, self._prev_trade, self._prev_trend)
if self.Position > 0 and prev_trigger1 >= prev_trigger2 and trigger1 < trigger2:
self.SellMarket(abs(self.Position))
elif self.Position < 0 and prev_trigger1 <= prev_trigger2 and trigger1 > trigger2:
self.BuyMarket(abs(self.Position))
else:
cross_up = self._prev_price <= self._prev_signal and price > signal
cross_down = self._prev_price >= self._prev_signal and price < signal
if self.Position == 0 and cross_up and state == 1:
self.BuyMarket()
elif self.Position == 0 and cross_down and state == 6:
self.SellMarket()
self._prev_price = price
self._prev_signal = signal
self._prev_trade = trade
self._prev_trend = trend
self._trim()
def _trend_state(self, price, trade, trend):
if price > trade and trade > trend:
return 1
if price < trade and trade < trend:
return 6
return 0
def _get_fld(self, cycle):
displacement = max(1, cycle // 2)
target = len(self._closes) - 1 - displacement
if target < 0:
return None
if not bool(self._smooth_fld.Value):
return self._closes[target]
smoothing = int(self._fld_smoothing.Value)
start = max(0, target - smoothing + 1)
values = self._closes[start:target + 1]
return sum(values) / len(values)
@staticmethod
def _resolve(trigger, price, signal, trade, trend):
key = trigger.lower()
if key == "signal":
return signal
if key == "trade":
return trade
if key == "trend":
return trend
return price
def _trim(self):
keep = max(int(self._trend_cycle.Value), int(self._trade_cycle.Value), int(self._signal_cycle.Value)) + int(self._fld_smoothing.Value) + 4
if len(self._closes) > keep:
del self._closes[0:len(self._closes) - keep]
def CreateClone(self):
return hurst_future_lines_of_demarcation_strategy()