Anomalous Holonomy Field Theory
固定 EMA20/EMA50、Wilder RSI14、MACD(12,26,9)、Wilder ATR、ボラティリティ正規化 ROC からなる6成分シグナルです。C は元の終値、A=ATR、clip(x,k)=max(-k,min(k,x))。全窓に現在の確定足を含みます。
取引ルール
T:両 EMA より上なら +1.5、EMA20 のみより上なら +1、両方より下なら -1.5、EMA20 のみより下なら -1、他は0。足 VWAP は VwapPeriod 本の sum(((High+Low+Close)/3)*Volume)/sum(Volume)。V=clip((C-VWAP)/A,2)。B=T+V が正のときだけ RSI が30未満なら +1.5、40未満なら +0.5、負のときだけ70超なら -1.5、60超なら -0.5 を R とし、他は0です。
M=clip((MACD-EMA9(MACD))/A,1)、Q=clip((C-C[RocPeriod 本前])/A,1) は同じ過去価格を分母にする ROC/ATR% です。S=T+V+R+M+Q。保有なしで S>=SignalThreshold なら Volume 買い、S<=-SignalThreshold なら売り。準備不足、ATR 非正、出来高ゼロでは建てません。新規建て足インデックスの間隔は CooldownBars を厳密に超える必要があります。
各発注前に ATR*AtrStopMultiplier を固定し、実約定値基準のネイティブなローカル成行ストップが足の間でも bid/ask に反応します。初期値は2 ATR。保有中は保護で決済し、逆シグナルで追加や反転をしません。新規待機期間はストップを遅らせず、利確目標はありません。
指標のウォームアップ完了後、確定足だけで新規シグナルを生成します。保有ポジションは決済まで管理し、未完了の成行注文がある間は重複発注しません。
パラメーター
| Parameter | Default |
|---|---|
SignalThreshold |
2 |
CooldownBars |
50 |
AtrLength |
14 |
AtrStopMultiplier |
2 |
RocPeriod |
10 |
VwapPeriod |
20 |
CandleType |
5m |
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>EMA, RSI, MACD, volatility-normalized ROC and bar VWAP composite with a frozen ATR stop.</summary>
public class AnomalousHolonomyFieldTheoryStrategy : Strategy
{
private readonly StrategyParam<decimal> _signalThreshold;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrStopMultiplier;
private readonly StrategyParam<int> _rocPeriod;
private readonly StrategyParam<int> _vwapPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _closes = new();
private readonly Queue<(decimal price, decimal volume)> _vwapBars = new();
private Order _pending;
private int _bar;
private int? _lastEntry;
/// <summary>Initializes the composite and risk parameters.</summary>
public AnomalousHolonomyFieldTheoryStrategy()
{
_signalThreshold = Param(nameof(SignalThreshold), 2m).SetGreaterThanZero()
.SetDisplay("SignalThreshold", "Absolute composite threshold", "Parameters");
_cooldownBars = Param(nameof(CooldownBars), 50).SetNotNegative()
.SetDisplay("CooldownBars", "Minimum finished bars between entries", "Parameters");
_atrLength = Param(nameof(AtrLength), 14).SetGreaterThanZero()
.SetDisplay("AtrLength", "Wilder ATR window", "Parameters");
_atrStopMultiplier = Param(nameof(AtrStopMultiplier), 2m).SetGreaterThanZero()
.SetDisplay("AtrStopMultiplier", "Entry-frozen ATR stop multiple", "Parameters");
_rocPeriod = Param(nameof(RocPeriod), 10).SetGreaterThanZero()
.SetDisplay("RocPeriod", "Rate-of-change interval", "Parameters");
_vwapPeriod = Param(nameof(VwapPeriod), 20).SetGreaterThanZero()
.SetDisplay("VwapPeriod", "Volume-weighted typical-price window", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("CandleType", "Finished signal candle interval", "Parameters");
}
/// <summary>Absolute composite threshold.</summary>
public decimal SignalThreshold
{
get => _signalThreshold.Value;
set => _signalThreshold.Value = value;
}
/// <summary>Minimum finished bars between entries.</summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>Wilder ATR window.</summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>Entry-frozen ATR stop multiple.</summary>
public decimal AtrStopMultiplier
{
get => _atrStopMultiplier.Value;
set => _atrStopMultiplier.Value = value;
}
/// <summary>Rate-of-change interval.</summary>
public int RocPeriod
{
get => _rocPeriod.Value;
set => _rocPeriod.Value = value;
}
/// <summary>Volume-weighted typical-price window.</summary>
public int VwapPeriod
{
get => _vwapPeriod.Value;
set => _vwapPeriod.Value = value;
}
/// <summary>Finished signal candle interval.</summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closes.Clear();
_vwapBars.Clear();
_pending = null;
_bar = 0;
_lastEntry = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema20 = new ExponentialMovingAverage { Length = 20 };
var ema50 = new ExponentialMovingAverage { Length = 50 };
var rsi = new RelativeStrengthIndex { Length = 14 };
var macd = new MovingAverageConvergenceDivergenceSignal();
macd.Macd.ShortMa.Length = 12;
macd.Macd.LongMa.Length = 26;
macd.SignalMa.Length = 9;
var atr = new AverageTrueRange { Length = AtrLength };
var candles = SubscribeCandles(CandleType);
candles.BindEx([ema20, ema50, rsi, macd, atr], ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, candles);
DrawIndicator(area, ema20);
DrawIndicator(area, ema50);
DrawOwnTrades(area);
}
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quote = new Subscription(DataType.Level1, Security);
quote.MarketData.BuildField = field;
SubscribeLevel1(quote).Start();
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished)
return;
_bar++;
_closes.Enqueue(candle.ClosePrice);
if (_closes.Count > RocPeriod + 1)
_closes.Dequeue();
_vwapBars.Enqueue(((candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m, candle.TotalVolume));
if (_vwapBars.Count > VwapPeriod)
_vwapBars.Dequeue();
if (values.Any(v => !v.IsFormed) || _closes.Count <= RocPeriod || _vwapBars.Count < VwapPeriod
|| values[3] is not MovingAverageConvergenceDivergenceSignalValue macd
|| macd.Macd is not decimal line || macd.Signal is not decimal signal)
return;
var atr = values[4].GetValue<decimal>();
var volume = _vwapBars.Sum(b => b.volume);
if (atr <= 0m || volume <= 0m || _closes.Peek() <= 0m)
return;
var close = candle.ClosePrice;
var fast = values[0].GetValue<decimal>();
var slow = values[1].GetValue<decimal>();
var rsi = values[2].GetValue<decimal>();
var composite = close > fast ? close > slow ? 1.5m : 1m : close < fast ? close < slow ? -1.5m : -1m : 0m;
var vwap = _vwapBars.Sum(b => b.price * b.volume) / volume;
composite += Math.Clamp((close - vwap) / atr, -2m, 2m);
if (rsi < 30m && composite > 0m)
composite += 1.5m;
else if (rsi < 40m && composite > 0m)
composite += 0.5m;
else if (rsi > 70m && composite < 0m)
composite -= 1.5m;
else if (rsi > 60m && composite < 0m)
composite -= 0.5m;
composite += Math.Clamp((line - signal) / atr, -1m, 1m);
// ROC / ATR% uses the same lagged price denominator, so it simplifies to a price change / ATR.
composite += Math.Clamp((close - _closes.Peek()) / atr, -1m, 1m);
if (Position != 0m || _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed)
|| _lastEntry is int lastEntry && _bar - lastEntry <= CooldownBars || !IsFormedAndOnlineAndAllowTrading()
|| Math.Abs(composite) < SignalThreshold)
return;
StartProtection(new Unit(), new Unit(atr * AtrStopMultiplier, UnitTypes.Absolute), useMarketOrders: true, isLocalStop: true);
_lastEntry = _bar;
_pending = CreateOrder(composite > 0m ? Sides.Buy : Sides.Sell, 0m, Volume);
_pending.Type = OrderTypes.Market;
_pending.Comment = "0522Entry";
RegisterOrder(_pending);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Decimal, Math, TimeSpan, Convert, InvalidOperationException
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex, ExponentialMovingAverage, AverageTrueRange, DecimalIndicatorValue, MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
from StockSharp.BusinessEntities import Subscription
from StockSharp.Messages import DataType, CandleStates, Level1Fields, OrderStates, OrderTypes, Sides, Unit, UnitTypes, ITickTradeMessage
class anomalous_holonomy_field_theory_strategy(Strategy):
"""Six-component signal with an entry-frozen ATR stop."""
def __init__(self):
super(anomalous_holonomy_field_theory_strategy, self).__init__()
self._signalThreshold = self.Param("SignalThreshold", Decimal(2)).SetGreaterThanZero() \
.SetDisplay("SignalThreshold", "Absolute composite threshold", "Parameters")
self._cooldownBars = self.Param("CooldownBars", 50).SetNotNegative() \
.SetDisplay("CooldownBars", "Minimum finished bars between entries", "Parameters")
self._atrLength = self.Param("AtrLength", 14).SetGreaterThanZero() \
.SetDisplay("AtrLength", "Wilder ATR window", "Parameters")
self._atrStopMultiplier = self.Param("AtrStopMultiplier", Decimal(2)).SetGreaterThanZero() \
.SetDisplay("AtrStopMultiplier", "Entry-frozen ATR stop multiple", "Parameters")
self._rocPeriod = self.Param("RocPeriod", 10).SetGreaterThanZero() \
.SetDisplay("RocPeriod", "Rate-of-change interval", "Parameters")
self._vwapPeriod = self.Param("VwapPeriod", 20).SetGreaterThanZero() \
.SetDisplay("VwapPeriod", "Volume-weighted typical-price window", "Parameters")
self._candleType = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("CandleType", "Finished signal candle interval", "Parameters")
self._clear_state()
@property
def SignalThreshold(self):
return self._signalThreshold.Value
@SignalThreshold.setter
def SignalThreshold(self, value):
self._signalThreshold.Value = value
@property
def CooldownBars(self):
return self._cooldownBars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldownBars.Value = value
@property
def AtrLength(self):
return self._atrLength.Value
@AtrLength.setter
def AtrLength(self, value):
self._atrLength.Value = value
@property
def AtrStopMultiplier(self):
return self._atrStopMultiplier.Value
@AtrStopMultiplier.setter
def AtrStopMultiplier(self, value):
self._atrStopMultiplier.Value = value
@property
def RocPeriod(self):
return self._rocPeriod.Value
@RocPeriod.setter
def RocPeriod(self, value):
self._rocPeriod.Value = value
@property
def VwapPeriod(self):
return self._vwapPeriod.Value
@VwapPeriod.setter
def VwapPeriod(self, value):
self._vwapPeriod.Value = value
@property
def CandleType(self):
return self._candleType.Value
@CandleType.setter
def CandleType(self, value):
self._candleType.Value = value
def _clear_state(self):
self._closes = []
self._vwap_bars = []
self._pending = None
self._bar = 0
self._last_entry = None
def OnReseted(self):
super(anomalous_holonomy_field_theory_strategy, self).OnReseted()
self._clear_state()
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, DataType.Level1)]
def OnStarted2(self, time):
super(anomalous_holonomy_field_theory_strategy, self).OnStarted2(time)
ema20 = ExponentialMovingAverage()
ema20.Length = 20
ema50 = ExponentialMovingAverage()
ema50.Length = 50
rsi = RelativeStrengthIndex()
rsi.Length = 14
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = 12
macd.Macd.LongMa.Length = 26
macd.SignalMa.Length = 9
atr = AverageTrueRange()
atr.Length = self.AtrLength
self._indicators = (ema20, ema50, rsi, macd, atr)
candles = self.SubscribeCandles(self.CandleType)
candles.BindEx(ema20, ema50, rsi, macd, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawIndicator(area, ema20)
self.DrawIndicator(area, ema50)
self.DrawOwnTrades(area)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quote = Subscription(DataType.Level1, self.Security)
quote.MarketData.BuildField = field
self.SubscribeLevel1(quote).Start()
@staticmethod
def _clip(value, limit):
return max(-limit, min(limit, value))
def _process_candle(self, candle, fast_value, slow_value, rsi_value, macd_value, atr_value):
if candle.State != CandleStates.Finished:
return
self._bar += 1
self._closes.append(candle.ClosePrice)
if len(self._closes) > self.RocPeriod + 1:
self._closes.pop(0)
self._vwap_bars.append(((candle.HighPrice + candle.LowPrice + candle.ClosePrice) / Decimal(3), candle.TotalVolume))
if len(self._vwap_bars) > self.VwapPeriod:
self._vwap_bars.pop(0)
if not all(i.IsFormed for i in self._indicators) or len(self._closes) <= self.RocPeriod or len(self._vwap_bars) < self.VwapPeriod or macd_value.Macd is None or macd_value.Signal is None:
return
atr = atr_value.GetValue[Decimal](None)
volume = sum((b[1] for b in self._vwap_bars), Decimal(0))
if atr <= 0 or volume <= 0 or self._closes[0] <= 0:
return
close = candle.ClosePrice
fast = fast_value.GetValue[Decimal](None)
slow = slow_value.GetValue[Decimal](None)
rsi = rsi_value.GetValue[Decimal](None)
composite = Decimal(1.5) if close > fast and close > slow else Decimal(1) if close > fast else Decimal(-1.5) if close < fast and close < slow else Decimal(-1) if close < fast else Decimal(0)
vwap = sum((b[0] * b[1] for b in self._vwap_bars), Decimal(0)) / volume
composite += self._clip((close - vwap) / atr, Decimal(2))
if rsi < 30 and composite > 0:
composite += Decimal(1.5)
elif rsi < 40 and composite > 0:
composite += Decimal(0.5)
elif rsi > 70 and composite < 0:
composite -= Decimal(1.5)
elif rsi > 60 and composite < 0:
composite -= Decimal(0.5)
composite += self._clip((macd_value.Macd - macd_value.Signal) / atr, Decimal(1))
composite += self._clip((close - self._closes[0]) / atr, Decimal(1))
pending = self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed)
if self.Position != 0 or pending or self._last_entry is not None and self._bar - self._last_entry <= self.CooldownBars or not self.IsFormedAndOnlineAndAllowTrading() or abs(composite) < self.SignalThreshold:
return
self.StartProtection(Unit(), Unit(atr * self.AtrStopMultiplier, UnitTypes.Absolute), useMarketOrders=True, isLocalStop=True)
self._last_entry = self._bar
self._pending = self.CreateOrder(Sides.Buy if composite > 0 else Sides.Sell, Decimal(0), self.Volume)
self._pending.Type = OrderTypes.Market
self._pending.Comment = "0522Entry"
self.RegisterOrder(self._pending)
def CreateClone(self):
return anomalous_holonomy_field_theory_strategy()