Anubis 策略
概述
Anubis 策略结合了多周期波动率与动量过滤,用于捕捉强烈反向冲击后的反弹行情。原始的 MT5 专家顾问在 H4 周期上应用过滤指标,在 M15 周期上完成入场判定。移植到 StockSharp 后,保留了这一结构,并利用高阶 API 提供更清晰的状态管理与可视化。
运行逻辑
- 时间框架
- 主信号周期:可配置的蜡烛类型(默认 15 分钟)。
- 高级确认周期:固定的 4 小时蜡烛,负责 CCI 与标准差计算。
- 指标组合
- 高级 CCI 检测超买 / 超卖区。
- 两个高级标准差衡量波动率并确定止盈距离。
- 主周期 MACD 提供动量方向交叉信号。
- 主周期 ATR 量化异常的蜡烛波幅,用于强制离场。
- 入场条件
- 做多: CCI 低于
-CciThreshold,MACD 主线向上穿越信号线,且上一根柱状图为负值。 - 做空: CCI 高于
+CciThreshold,MACD 主线向下穿越信号线,且上一根柱状图为正值。 - 若存在相反仓位,则先平仓后再按
SpacingPips要求叠加同向仓位。
- 做多: CCI 低于
- 仓位管理
- 最多允许
MaxLongPositions或MaxShortPositions个分批建仓,每批数量为TradeVolume。 - 止损、止盈根据品种
PriceStep与高级标准差换算自参数的“点”值。 - 当浮盈达到
BreakevenPips时,保护止损提升至盈亏平衡价位。
- 最多允许
- 离场规则
- 严格止损:每根收盘蜡烛检查止损与止盈触发。
- 波动率退出:若上一根蜡烛波幅大于
CloseAtrMultiplier × ATR,立即平仓。 - 动量退出:当盈利超过
ThresholdPips且 MACD 动量反向时出场。
参数
| 名称 | 默认值 | 说明 |
|---|---|---|
TradeVolume |
1 | 每次下单的数量。 |
CciThreshold |
80 | H4 CCI 的极值阈值。 |
CciPeriod |
11 | 高级 CCI 的周期长度。 |
StopLossPips |
100 | 以点表示的止损距离(0 表示关闭初始止损)。 |
BreakevenPips |
65 | 将止损推至保本所需的收益点数。 |
ThresholdPips |
28 | 触发 MACD 反向离场所需的额外利润缓冲。 |
TakeStdMultiplier |
2.9 | 计算止盈距离时乘以慢速标准差的系数。 |
CloseAtrMultiplier |
2 | ATR 放大倍数,用于判断异常蜡烛退出。 |
SpacingPips |
20 | 同向加仓之间的最小价格间隔。 |
MaxLongPositions |
2 | 最多并存的多单数量。 |
MaxShortPositions |
2 | 最多并存的空单数量。 |
MacdFastLength |
20 | MACD 快速 EMA 周期。 |
MacdSlowLength |
50 | MACD 慢速 EMA 周期。 |
MacdSignalLength |
2 | MACD 信号线平滑长度。 |
AtrLength |
12 | 主周期 ATR 的计算长度。 |
StdFastLength |
20 | 快速标准差的周期。 |
StdSlowLength |
30 | 慢速标准差的周期,用于确定止盈。 |
CandleType |
15 分钟 | 主信号周期的蜡烛类型。 |
使用建议
- 高级周期固定为 4 小时;如果需要适配其他市场节奏,可调整
CandleType以改变主信号频率。 - StockSharp 默认按净头寸管理,策略不会同时持有多空方向;出现反向信号时会先平仓再建仓。
- 标准差采用 StockSharp 的实现方式,
StdSlowLength近似原版中基于 EMA 的波动度计算。 - 请确保交易品种设置了正确的
PriceStep,否则以点为单位的参数无法准确换算成价格距离。
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Converted Anubis strategy that combines higher timeframe CCI and standard deviation with MACD signals.
/// </summary>
public class AnubisStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _cciThreshold;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _breakevenPips;
private readonly StrategyParam<decimal> _thresholdPips;
private readonly StrategyParam<decimal> _takeStdMultiplier;
private readonly StrategyParam<decimal> _closeAtrMultiplier;
private readonly StrategyParam<decimal> _spacingPips;
private readonly StrategyParam<int> _maxLongPositions;
private readonly StrategyParam<int> _maxShortPositions;
private readonly StrategyParam<int> _macdFastLength;
private readonly StrategyParam<int> _macdSlowLength;
private readonly StrategyParam<int> _macdSignalLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<int> _stdFastLength;
private readonly StrategyParam<int> _stdSlowLength;
private readonly StrategyParam<DataType> _candleType;
private readonly DataType _higherTimeFrame = TimeSpan.FromHours(4).TimeFrame();
private AverageTrueRange _atrIndicator = null!;
private CommodityChannelIndex _cciIndicator = null!;
private StandardDeviation _fastStdDev = null!;
private StandardDeviation _slowStdDev = null!;
private MovingAverageConvergenceDivergenceSignal _macdIndicator = null!;
private decimal _lastAtr;
private bool _atrReady;
private decimal _cciValue;
private decimal _stdFastValue;
private decimal _stdSlowValue;
private bool _higherReady;
private decimal _macdMainPrev1;
private decimal _macdMainPrev2;
private decimal _macdSignalPrev1;
private decimal _macdSignalPrev2;
private int _macdSamples;
private decimal _prevCandleOpen;
private decimal _prevCandleClose;
private bool _hasPrevCandle;
private decimal _adjustedPoint;
private decimal _stopLossDistance;
private decimal _breakevenDistance;
private decimal _thresholdDistance;
private decimal _spacingDistance;
private decimal _longStopPrice;
private decimal _longTakePrice;
private bool _longBreakevenActivated;
private int _longEntries;
private DateTimeOffset? _lastLongSignalTime;
private decimal _lastLongPrice;
private decimal _shortStopPrice;
private decimal _shortTakePrice;
private bool _shortBreakevenActivated;
private int _shortEntries;
private DateTimeOffset? _lastShortSignalTime;
private decimal _lastShortPrice;
private decimal _entryPrice;
/// <summary>
/// Trade volume for each entry.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// CCI threshold for overbought/oversold detection.
/// </summary>
public decimal CciThreshold
{
get => _cciThreshold.Value;
set => _cciThreshold.Value = value;
}
/// <summary>
/// CCI calculation period.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Breakeven activation distance in pips.
/// </summary>
public decimal BreakevenPips
{
get => _breakevenPips.Value;
set => _breakevenPips.Value = value;
}
/// <summary>
/// Profit locking threshold in pips for MACD exit.
/// </summary>
public decimal ThresholdPips
{
get => _thresholdPips.Value;
set => _thresholdPips.Value = value;
}
/// <summary>
/// Multiplier applied to the slow standard deviation for take-profit.
/// </summary>
public decimal TakeStdMultiplier
{
get => _takeStdMultiplier.Value;
set => _takeStdMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier used for candle range exit.
/// </summary>
public decimal CloseAtrMultiplier
{
get => _closeAtrMultiplier.Value;
set => _closeAtrMultiplier.Value = value;
}
/// <summary>
/// Minimum spacing between sequential entries in pips.
/// </summary>
public decimal SpacingPips
{
get => _spacingPips.Value;
set => _spacingPips.Value = value;
}
/// <summary>
/// Maximum number of simultaneous long entries.
/// </summary>
public int MaxLongPositions
{
get => _maxLongPositions.Value;
set => _maxLongPositions.Value = value;
}
/// <summary>
/// Maximum number of simultaneous short entries.
/// </summary>
public int MaxShortPositions
{
get => _maxShortPositions.Value;
set => _maxShortPositions.Value = value;
}
/// <summary>
/// Fast EMA length for MACD.
/// </summary>
public int MacdFastLength
{
get => _macdFastLength.Value;
set => _macdFastLength.Value = value;
}
/// <summary>
/// Slow EMA length for MACD.
/// </summary>
public int MacdSlowLength
{
get => _macdSlowLength.Value;
set => _macdSlowLength.Value = value;
}
/// <summary>
/// Signal smoothing length for MACD.
/// </summary>
public int MacdSignalLength
{
get => _macdSignalLength.Value;
set => _macdSignalLength.Value = value;
}
/// <summary>
/// ATR calculation length.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// Fast standard deviation length.
/// </summary>
public int StdFastLength
{
get => _stdFastLength.Value;
set => _stdFastLength.Value = value;
}
/// <summary>
/// Slow standard deviation length.
/// </summary>
public int StdSlowLength
{
get => _stdSlowLength.Value;
set => _stdSlowLength.Value = value;
}
/// <summary>
/// Main candle type used for MACD and ATR.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="AnubisStrategy"/> with default parameters.
/// </summary>
public AnubisStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order size used for entries", "Trading");
_cciThreshold = Param(nameof(CciThreshold), 80m)
.SetGreaterThanZero()
.SetDisplay("CCI Threshold", "Absolute CCI level used to detect extremes", "Indicators");
_cciPeriod = Param(nameof(CciPeriod), 11)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI lookback on the higher timeframe", "Indicators");
_stopLossPips = Param(nameof(StopLossPips), 500m)
.SetDisplay("Stop Loss (pips)", "Stop-loss distance measured in pips", "Risk");
_breakevenPips = Param(nameof(BreakevenPips), 300m)
.SetDisplay("Breakeven (pips)", "Distance to move stop to entry", "Risk");
_thresholdPips = Param(nameof(ThresholdPips), 200m)
.SetDisplay("MACD Exit Threshold (pips)", "Extra profit required before MACD exit", "Risk");
_takeStdMultiplier = Param(nameof(TakeStdMultiplier), 2.9m)
.SetGreaterThanZero()
.SetDisplay("StdDev Multiplier", "Multiplier for higher timeframe standard deviation", "Risk");
_closeAtrMultiplier = Param(nameof(CloseAtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Previous candle range multiplier for exits", "Risk");
_spacingPips = Param(nameof(SpacingPips), 20m)
.SetGreaterThanZero()
.SetDisplay("Entry Spacing (pips)", "Minimum distance between consecutive entries", "Trading");
_maxLongPositions = Param(nameof(MaxLongPositions), 1)
.SetGreaterThanZero()
.SetDisplay("Max Long Entries", "Maximum stacked long positions", "Trading");
_maxShortPositions = Param(nameof(MaxShortPositions), 1)
.SetGreaterThanZero()
.SetDisplay("Max Short Entries", "Maximum stacked short positions", "Trading");
_macdFastLength = Param(nameof(MacdFastLength), 20)
.SetGreaterThanZero()
.SetDisplay("MACD Fast Length", "Fast EMA period for MACD", "Indicators");
_macdSlowLength = Param(nameof(MacdSlowLength), 50)
.SetGreaterThanZero()
.SetDisplay("MACD Slow Length", "Slow EMA period for MACD", "Indicators");
_macdSignalLength = Param(nameof(MacdSignalLength), 2)
.SetGreaterThanZero()
.SetDisplay("MACD Signal Length", "Signal smoothing for MACD", "Indicators");
_atrLength = Param(nameof(AtrLength), 12)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR lookback on the main timeframe", "Indicators");
_stdFastLength = Param(nameof(StdFastLength), 20)
.SetGreaterThanZero()
.SetDisplay("Fast StdDev Length", "SMA based standard deviation period", "Indicators");
_stdSlowLength = Param(nameof(StdSlowLength), 30)
.SetGreaterThanZero()
.SetDisplay("Slow StdDev Length", "Secondary standard deviation period used for take-profit", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Signal Candle Type", "Timeframe used for MACD and ATR", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
yield return (Security, _higherTimeFrame);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastAtr = 0m;
_atrReady = false;
_cciValue = 0m;
_stdFastValue = 0m;
_stdSlowValue = 0m;
_higherReady = false;
_macdMainPrev1 = 0m;
_macdMainPrev2 = 0m;
_macdSignalPrev1 = 0m;
_macdSignalPrev2 = 0m;
_macdSamples = 0;
_prevCandleOpen = 0m;
_prevCandleClose = 0m;
_hasPrevCandle = false;
_adjustedPoint = 0m;
_stopLossDistance = 0m;
_breakevenDistance = 0m;
_thresholdDistance = 0m;
_spacingDistance = 0m;
_longStopPrice = 0m;
_longTakePrice = 0m;
_longBreakevenActivated = false;
_longEntries = 0;
_lastLongSignalTime = null;
_lastLongPrice = 0m;
_shortStopPrice = 0m;
_shortTakePrice = 0m;
_shortBreakevenActivated = false;
_shortEntries = 0;
_lastShortSignalTime = null;
_lastShortPrice = 0m;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume;
// no protection
InitializeIndicators();
InitializeDistances();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_atrIndicator, (candle, atrValue) =>
{
if (candle.State != CandleStates.Finished)
return;
_lastAtr = atrValue;
_atrReady = _atrIndicator.IsFormed;
})
.BindEx(_macdIndicator, ProcessMainCandle)
.Start();
SubscribeCandles(_higherTimeFrame)
.Bind(_fastStdDev, _slowStdDev, _cciIndicator, ProcessHigherCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macdIndicator);
DrawIndicator(area, _cciIndicator);
DrawOwnTrades(area);
}
}
private void InitializeIndicators()
{
// Create indicator instances based on the latest parameters.
_atrIndicator = new AverageTrueRange { Length = AtrLength };
_cciIndicator = new CommodityChannelIndex { Length = CciPeriod };
_fastStdDev = new StandardDeviation { Length = StdFastLength };
_slowStdDev = new StandardDeviation { Length = StdSlowLength };
_macdIndicator = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFastLength },
LongMa = { Length = MacdSlowLength },
},
SignalMa = { Length = MacdSignalLength }
};
}
private void InitializeDistances()
{
// Normalize pip-based settings into absolute price distances.
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
step = 0.0001m;
_adjustedPoint = step;
if (step > 0m && step < 0.01m)
_adjustedPoint = step * 10m;
_stopLossDistance = StopLossPips * _adjustedPoint;
_breakevenDistance = BreakevenPips * _adjustedPoint;
_thresholdDistance = ThresholdPips * _adjustedPoint;
_spacingDistance = SpacingPips * _adjustedPoint;
}
private void ProcessHigherCandle(ICandleMessage candle, decimal fastStd, decimal slowStd, decimal cci)
{
if (candle.State != CandleStates.Finished)
return;
// Store higher timeframe indicator values for the main signal evaluation.
_stdFastValue = fastStd;
_stdSlowValue = slowStd;
_cciValue = cci;
_higherReady = _fastStdDev.IsFormed && _slowStdDev.IsFormed && _cciIndicator.IsFormed;
}
private void ProcessMainCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdTyped)
return;
if (macdTyped.Macd is not decimal macdCurrent || macdTyped.Signal is not decimal signalCurrent)
return;
// Reset cached targets whenever the strategy becomes flat.
if (Position <= 0m && _longEntries > 0)
ResetLongTargets();
if (Position >= 0m && _shortEntries > 0)
ResetShortTargets();
var macd1 = _macdMainPrev1;
var macd2 = _macdMainPrev2;
var signal1 = _macdSignalPrev1;
var signal2 = _macdSignalPrev2;
var hasMacdHistory = _macdSamples >= 2;
var price = candle.ClosePrice;
// Wait until all indicators provide valid data before trading.
if (!_macdIndicator.IsFormed || !_higherReady || !_atrReady || !hasMacdHistory || _stdSlowValue <= 0m)
{
UpdateStateAfterProcess(macdCurrent, signalCurrent, candle);
return;
}
var cci = _cciValue;
var takeDistance = TakeStdMultiplier * _stdSlowValue;
// Evaluate entry signals on MACD crosses and higher timeframe CCI extremes.
var openBuy = cci < -CciThreshold && macd2 <= signal2 && macd1 > signal1 && macd1 < 0m;
var openSell = cci > CciThreshold && macd2 >= signal2 && macd1 < signal1 && macd1 > 0m;
if (openBuy)
{
// Close opposite exposure before opening a new long.
if (Position < 0m)
{
BuyMarket();
ResetShortTargets();
}
// Apply stacking rules and spacing filters.
var allowEntry = Position >= 0m && _longEntries < MaxLongPositions && takeDistance > 0m;
var spacedEnough = _lastLongPrice == 0m || Math.Abs(price - _lastLongPrice) > _spacingDistance;
var newBar = _lastLongSignalTime != candle.OpenTime;
if (allowEntry && spacedEnough && newBar)
{
BuyMarket();
_entryPrice = price;
_longEntries++;
_lastLongPrice = price;
_lastLongSignalTime = candle.OpenTime;
_longStopPrice = _stopLossDistance > 0m ? price - _stopLossDistance : 0m;
_longTakePrice = takeDistance > 0m ? price + takeDistance : 0m;
_longBreakevenActivated = false;
}
}
else if (openSell)
{
// Close opposite exposure before opening a new short.
if (Position > 0m)
{
SellMarket();
ResetLongTargets();
}
// Apply stacking rules and spacing filters.
var allowEntry = Position <= 0m && _shortEntries < MaxShortPositions && takeDistance > 0m;
var spacedEnough = _lastShortPrice == 0m || Math.Abs(price - _lastShortPrice) > _spacingDistance;
var newBar = _lastShortSignalTime != candle.OpenTime;
if (allowEntry && spacedEnough && newBar)
{
SellMarket();
_entryPrice = price;
_shortEntries++;
_lastShortPrice = price;
_lastShortSignalTime = candle.OpenTime;
_shortStopPrice = _stopLossDistance > 0m ? price + _stopLossDistance : 0m;
_shortTakePrice = takeDistance > 0m ? price - takeDistance : 0m;
_shortBreakevenActivated = false;
}
}
UpdateBreakeven(price);
if (Position > 0m)
{
var prevRange = _hasPrevCandle ? _prevCandleClose - _prevCandleOpen : 0m;
var exitByRange = _hasPrevCandle && prevRange > CloseAtrMultiplier * _lastAtr;
var exitByMacd = macd1 < macd2 && price - _entryPrice > _thresholdDistance;
// Check range-based and MACD-based exit conditions.
if (exitByRange || exitByMacd)
{
SellMarket();
ResetLongTargets();
}
else
{
CheckLongStops(price);
}
}
else if (Position < 0m)
{
var prevRange = _hasPrevCandle ? _prevCandleOpen - _prevCandleClose : 0m;
var exitByRange = _hasPrevCandle && prevRange > CloseAtrMultiplier * _lastAtr;
var exitByMacd = macd1 > macd2 && _entryPrice - price > _thresholdDistance;
if (exitByRange || exitByMacd)
{
BuyMarket();
ResetShortTargets();
}
else
{
CheckShortStops(price);
}
}
else
{
// Clear cached targets when no positions are open.
ResetLongTargets();
ResetShortTargets();
}
UpdateStateAfterProcess(macdCurrent, signalCurrent, candle);
}
private void UpdateBreakeven(decimal price)
{
// Long side breakeven management.
if (Position > 0m && !_longBreakevenActivated && _breakevenDistance > 0m && price - _breakevenDistance > _entryPrice && _longStopPrice > 0m)
{
_longBreakevenActivated = true;
_longStopPrice = _entryPrice;
}
else if (Position <= 0m)
{
_longBreakevenActivated = false;
}
// Short side breakeven management.
if (Position < 0m && !_shortBreakevenActivated && _breakevenDistance > 0m && price + _breakevenDistance < _entryPrice && _shortStopPrice > 0m)
{
_shortBreakevenActivated = true;
_shortStopPrice = _entryPrice;
}
else if (Position >= 0m)
{
_shortBreakevenActivated = false;
}
}
private void CheckLongStops(decimal price)
{
if (Position <= 0m)
return;
// Exit long positions when price hits the take-profit level.
if (_longTakePrice > 0m && price >= _longTakePrice)
{
SellMarket();
ResetLongTargets();
return;
}
// Exit long positions when price returns to the protective stop.
if (_longStopPrice > 0m && price <= _longStopPrice)
{
SellMarket();
ResetLongTargets();
}
}
private void CheckShortStops(decimal price)
{
if (Position >= 0m)
return;
// Exit short positions when price hits the take-profit level.
if (_shortTakePrice > 0m && price <= _shortTakePrice)
{
BuyMarket();
ResetShortTargets();
return;
}
// Exit short positions when price returns to the protective stop.
if (_shortStopPrice > 0m && price >= _shortStopPrice)
{
BuyMarket();
ResetShortTargets();
}
}
private void ResetLongTargets()
{
if (Position > 0m)
return;
// Clear long-specific cached values once the position is closed.
_longStopPrice = 0m;
_longTakePrice = 0m;
_longBreakevenActivated = false;
_longEntries = 0;
_lastLongPrice = 0m;
_lastLongSignalTime = null;
}
private void ResetShortTargets()
{
if (Position < 0m)
return;
// Clear short-specific cached values once the position is closed.
_shortStopPrice = 0m;
_shortTakePrice = 0m;
_shortBreakevenActivated = false;
_shortEntries = 0;
_lastShortPrice = 0m;
_lastShortSignalTime = null;
}
private void UpdateStateAfterProcess(decimal macdCurrent, decimal signalCurrent, ICandleMessage candle)
{
// Shift stored MACD values and remember the candle data for the next iteration.
_macdMainPrev2 = _macdMainPrev1;
_macdMainPrev1 = macdCurrent;
_macdSignalPrev2 = _macdSignalPrev1;
_macdSignalPrev1 = signalCurrent;
if (_macdSamples < 2)
_macdSamples++;
_prevCandleOpen = candle.OpenPrice;
_prevCandleClose = candle.ClosePrice;
_hasPrevCandle = true;
}
}
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
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
AverageTrueRange, CommodityChannelIndex, StandardDeviation,
MovingAverageConvergenceDivergenceSignal
)
class anubis_strategy(Strategy):
"""Anubis: CCI + StdDev on higher TF combined with MACD signals on main TF, with breakeven and stacking."""
def __init__(self):
super(anubis_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Trade Volume", "Order size used for entries", "Trading")
self._cci_threshold = self.Param("CciThreshold", 80.0) \
.SetGreaterThanZero() \
.SetDisplay("CCI Threshold", "Absolute CCI level used to detect extremes", "Indicators")
self._cci_period = self.Param("CciPeriod", 11) \
.SetGreaterThanZero() \
.SetDisplay("CCI Period", "CCI lookback on the higher timeframe", "Indicators")
self._stop_loss_pips = self.Param("StopLossPips", 500.0) \
.SetDisplay("Stop Loss (pips)", "Stop-loss distance measured in pips", "Risk")
self._breakeven_pips = self.Param("BreakevenPips", 300.0) \
.SetDisplay("Breakeven (pips)", "Distance to move stop to entry", "Risk")
self._threshold_pips = self.Param("ThresholdPips", 200.0) \
.SetDisplay("MACD Exit Threshold (pips)", "Extra profit required before MACD exit", "Risk")
self._take_std_multiplier = self.Param("TakeStdMultiplier", 2.9) \
.SetGreaterThanZero() \
.SetDisplay("StdDev Multiplier", "Multiplier for higher timeframe standard deviation", "Risk")
self._close_atr_multiplier = self.Param("CloseAtrMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Multiplier", "Previous candle range multiplier for exits", "Risk")
self._spacing_pips = self.Param("SpacingPips", 20.0) \
.SetGreaterThanZero() \
.SetDisplay("Entry Spacing (pips)", "Minimum distance between consecutive entries", "Trading")
self._max_long_positions = self.Param("MaxLongPositions", 1) \
.SetGreaterThanZero() \
.SetDisplay("Max Long Entries", "Maximum stacked long positions", "Trading")
self._max_short_positions = self.Param("MaxShortPositions", 1) \
.SetGreaterThanZero() \
.SetDisplay("Max Short Entries", "Maximum stacked short positions", "Trading")
self._macd_fast_length = self.Param("MacdFastLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("MACD Fast Length", "Fast EMA period for MACD", "Indicators")
self._macd_slow_length = self.Param("MacdSlowLength", 50) \
.SetGreaterThanZero() \
.SetDisplay("MACD Slow Length", "Slow EMA period for MACD", "Indicators")
self._macd_signal_length = self.Param("MacdSignalLength", 2) \
.SetGreaterThanZero() \
.SetDisplay("MACD Signal Length", "Signal smoothing for MACD", "Indicators")
self._atr_length = self.Param("AtrLength", 12) \
.SetGreaterThanZero() \
.SetDisplay("ATR Length", "ATR lookback on the main timeframe", "Indicators")
self._std_fast_length = self.Param("StdFastLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Fast StdDev Length", "SMA based standard deviation period", "Indicators")
self._std_slow_length = self.Param("StdSlowLength", 30) \
.SetGreaterThanZero() \
.SetDisplay("Slow StdDev Length", "Secondary standard deviation period used for take-profit", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Signal Candle Type", "Timeframe used for MACD and ATR", "General")
self._higher_time_frame = DataType.TimeFrame(TimeSpan.FromHours(4))
self._last_atr = 0.0
self._atr_ready = False
self._cci_value = 0.0
self._std_fast_value = 0.0
self._std_slow_value = 0.0
self._higher_ready = False
self._macd_main_prev1 = 0.0
self._macd_main_prev2 = 0.0
self._macd_signal_prev1 = 0.0
self._macd_signal_prev2 = 0.0
self._macd_samples = 0
self._prev_candle_open = 0.0
self._prev_candle_close = 0.0
self._has_prev_candle = False
self._adjusted_point = 0.0
self._stop_loss_distance = 0.0
self._breakeven_distance = 0.0
self._threshold_distance = 0.0
self._spacing_distance = 0.0
self._long_stop_price = 0.0
self._long_take_price = 0.0
self._long_breakeven_activated = False
self._long_entries = 0
self._last_long_signal_time = None
self._last_long_price = 0.0
self._short_stop_price = 0.0
self._short_take_price = 0.0
self._short_breakeven_activated = False
self._short_entries = 0
self._last_short_signal_time = None
self._last_short_price = 0.0
self._entry_price = 0.0
@property
def TradeVolume(self):
return float(self._trade_volume.Value)
@property
def CciThreshold(self):
return float(self._cci_threshold.Value)
@property
def CciPeriod(self):
return int(self._cci_period.Value)
@property
def StopLossPips(self):
return float(self._stop_loss_pips.Value)
@property
def BreakevenPips(self):
return float(self._breakeven_pips.Value)
@property
def ThresholdPips(self):
return float(self._threshold_pips.Value)
@property
def TakeStdMultiplier(self):
return float(self._take_std_multiplier.Value)
@property
def CloseAtrMultiplier(self):
return float(self._close_atr_multiplier.Value)
@property
def SpacingPips(self):
return float(self._spacing_pips.Value)
@property
def MaxLongPositions(self):
return int(self._max_long_positions.Value)
@property
def MaxShortPositions(self):
return int(self._max_short_positions.Value)
@property
def MacdFastLength(self):
return int(self._macd_fast_length.Value)
@property
def MacdSlowLength(self):
return int(self._macd_slow_length.Value)
@property
def MacdSignalLength(self):
return int(self._macd_signal_length.Value)
@property
def AtrLength(self):
return int(self._atr_length.Value)
@property
def StdFastLength(self):
return int(self._std_fast_length.Value)
@property
def StdSlowLength(self):
return int(self._std_slow_length.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _init_distances(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 0.0001
self._adjusted_point = step
if step > 0 and step < 0.01:
self._adjusted_point = step * 10.0
self._stop_loss_distance = self.StopLossPips * self._adjusted_point
self._breakeven_distance = self.BreakevenPips * self._adjusted_point
self._threshold_distance = self.ThresholdPips * self._adjusted_point
self._spacing_distance = self.SpacingPips * self._adjusted_point
def OnStarted2(self, time):
super(anubis_strategy, self).OnStarted2(time)
self._last_atr = 0.0
self._atr_ready = False
self._cci_value = 0.0
self._std_fast_value = 0.0
self._std_slow_value = 0.0
self._higher_ready = False
self._macd_main_prev1 = 0.0
self._macd_main_prev2 = 0.0
self._macd_signal_prev1 = 0.0
self._macd_signal_prev2 = 0.0
self._macd_samples = 0
self._prev_candle_open = 0.0
self._prev_candle_close = 0.0
self._has_prev_candle = False
self._long_stop_price = 0.0
self._long_take_price = 0.0
self._long_breakeven_activated = False
self._long_entries = 0
self._last_long_signal_time = None
self._last_long_price = 0.0
self._short_stop_price = 0.0
self._short_take_price = 0.0
self._short_breakeven_activated = False
self._short_entries = 0
self._last_short_signal_time = None
self._last_short_price = 0.0
self._entry_price = 0.0
self._init_distances()
self._atr_indicator = AverageTrueRange()
self._atr_indicator.Length = self.AtrLength
self._cci_indicator = CommodityChannelIndex()
self._cci_indicator.Length = self.CciPeriod
self._fast_std_dev = StandardDeviation()
self._fast_std_dev.Length = self.StdFastLength
self._slow_std_dev = StandardDeviation()
self._slow_std_dev.Length = self.StdSlowLength
self._macd_indicator = MovingAverageConvergenceDivergenceSignal()
self._macd_indicator.Macd.ShortMa.Length = self.MacdFastLength
self._macd_indicator.Macd.LongMa.Length = self.MacdSlowLength
self._macd_indicator.SignalMa.Length = self.MacdSignalLength
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(self._atr_indicator, self._process_atr_candle) \
.BindEx(self._macd_indicator, self._process_main_candle) \
.Start()
self.SubscribeCandles(self._higher_time_frame) \
.Bind(self._fast_std_dev, self._slow_std_dev, self._cci_indicator, self._process_higher_candle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._macd_indicator)
self.DrawIndicator(area, self._cci_indicator)
self.DrawOwnTrades(area)
def _process_atr_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
self._last_atr = float(atr_value)
self._atr_ready = self._atr_indicator.IsFormed
def _process_higher_candle(self, candle, fast_std, slow_std, cci):
if candle.State != CandleStates.Finished:
return
self._std_fast_value = float(fast_std)
self._std_slow_value = float(slow_std)
self._cci_value = float(cci)
self._higher_ready = self._fast_std_dev.IsFormed and self._slow_std_dev.IsFormed and self._cci_indicator.IsFormed
def _process_main_candle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
macd_v = macd_value.Macd
signal_v = macd_value.Signal
if macd_v is None or signal_v is None:
return
macd_current = float(macd_v)
signal_current = float(signal_v)
# Reset cached targets when strategy becomes flat
if self.Position <= 0 and self._long_entries > 0:
self._reset_long_targets()
if self.Position >= 0 and self._short_entries > 0:
self._reset_short_targets()
macd1 = self._macd_main_prev1
macd2 = self._macd_main_prev2
signal1 = self._macd_signal_prev1
signal2 = self._macd_signal_prev2
has_macd_history = self._macd_samples >= 2
price = float(candle.ClosePrice)
if not self._macd_indicator.IsFormed or not self._higher_ready or not self._atr_ready or not has_macd_history or self._std_slow_value <= 0:
self._update_state_after(macd_current, signal_current, candle)
return
cci = self._cci_value
take_distance = self.TakeStdMultiplier * self._std_slow_value
# Evaluate entry signals
open_buy = cci < -self.CciThreshold and macd2 <= signal2 and macd1 > signal1 and macd1 < 0
open_sell = cci > self.CciThreshold and macd2 >= signal2 and macd1 < signal1 and macd1 > 0
if open_buy:
if self.Position < 0:
self.BuyMarket()
self._reset_short_targets()
allow_entry = self.Position >= 0 and self._long_entries < self.MaxLongPositions and take_distance > 0
spaced_enough = self._last_long_price == 0 or abs(price - self._last_long_price) > self._spacing_distance
new_bar = self._last_long_signal_time is None or self._last_long_signal_time != candle.OpenTime
if allow_entry and spaced_enough and new_bar:
self.BuyMarket()
self._entry_price = price
self._long_entries += 1
self._last_long_price = price
self._last_long_signal_time = candle.OpenTime
self._long_stop_price = price - self._stop_loss_distance if self._stop_loss_distance > 0 else 0.0
self._long_take_price = price + take_distance if take_distance > 0 else 0.0
self._long_breakeven_activated = False
elif open_sell:
if self.Position > 0:
self.SellMarket()
self._reset_long_targets()
allow_entry = self.Position <= 0 and self._short_entries < self.MaxShortPositions and take_distance > 0
spaced_enough = self._last_short_price == 0 or abs(price - self._last_short_price) > self._spacing_distance
new_bar = self._last_short_signal_time is None or self._last_short_signal_time != candle.OpenTime
if allow_entry and spaced_enough and new_bar:
self.SellMarket()
self._entry_price = price
self._short_entries += 1
self._last_short_price = price
self._last_short_signal_time = candle.OpenTime
self._short_stop_price = price + self._stop_loss_distance if self._stop_loss_distance > 0 else 0.0
self._short_take_price = price - take_distance if take_distance > 0 else 0.0
self._short_breakeven_activated = False
self._update_breakeven(price)
if self.Position > 0:
prev_range = self._prev_candle_close - self._prev_candle_open if self._has_prev_candle else 0.0
exit_by_range = self._has_prev_candle and prev_range > self.CloseAtrMultiplier * self._last_atr
exit_by_macd = macd1 < macd2 and price - self._entry_price > self._threshold_distance
if exit_by_range or exit_by_macd:
self.SellMarket()
self._reset_long_targets()
else:
self._check_long_stops(price)
elif self.Position < 0:
prev_range = self._prev_candle_open - self._prev_candle_close if self._has_prev_candle else 0.0
exit_by_range = self._has_prev_candle and prev_range > self.CloseAtrMultiplier * self._last_atr
exit_by_macd = macd1 > macd2 and self._entry_price - price > self._threshold_distance
if exit_by_range or exit_by_macd:
self.BuyMarket()
self._reset_short_targets()
else:
self._check_short_stops(price)
else:
self._reset_long_targets()
self._reset_short_targets()
self._update_state_after(macd_current, signal_current, candle)
def _update_breakeven(self, price):
if self.Position > 0 and not self._long_breakeven_activated and self._breakeven_distance > 0:
if price - self._breakeven_distance > self._entry_price and self._long_stop_price > 0:
self._long_breakeven_activated = True
self._long_stop_price = self._entry_price
elif self.Position <= 0:
self._long_breakeven_activated = False
if self.Position < 0 and not self._short_breakeven_activated and self._breakeven_distance > 0:
if price + self._breakeven_distance < self._entry_price and self._short_stop_price > 0:
self._short_breakeven_activated = True
self._short_stop_price = self._entry_price
elif self.Position >= 0:
self._short_breakeven_activated = False
def _check_long_stops(self, price):
if self.Position <= 0:
return
if self._long_take_price > 0 and price >= self._long_take_price:
self.SellMarket()
self._reset_long_targets()
return
if self._long_stop_price > 0 and price <= self._long_stop_price:
self.SellMarket()
self._reset_long_targets()
def _check_short_stops(self, price):
if self.Position >= 0:
return
if self._short_take_price > 0 and price <= self._short_take_price:
self.BuyMarket()
self._reset_short_targets()
return
if self._short_stop_price > 0 and price >= self._short_stop_price:
self.BuyMarket()
self._reset_short_targets()
def _reset_long_targets(self):
if self.Position > 0:
return
self._long_stop_price = 0.0
self._long_take_price = 0.0
self._long_breakeven_activated = False
self._long_entries = 0
self._last_long_price = 0.0
self._last_long_signal_time = None
def _reset_short_targets(self):
if self.Position < 0:
return
self._short_stop_price = 0.0
self._short_take_price = 0.0
self._short_breakeven_activated = False
self._short_entries = 0
self._last_short_price = 0.0
self._last_short_signal_time = None
def _update_state_after(self, macd_current, signal_current, candle):
self._macd_main_prev2 = self._macd_main_prev1
self._macd_main_prev1 = macd_current
self._macd_signal_prev2 = self._macd_signal_prev1
self._macd_signal_prev1 = signal_current
if self._macd_samples < 2:
self._macd_samples += 1
self._prev_candle_open = float(candle.OpenPrice)
self._prev_candle_close = float(candle.ClosePrice)
self._has_prev_candle = True
def OnReseted(self):
super(anubis_strategy, self).OnReseted()
self._last_atr = 0.0
self._atr_ready = False
self._cci_value = 0.0
self._std_fast_value = 0.0
self._std_slow_value = 0.0
self._higher_ready = False
self._macd_main_prev1 = 0.0
self._macd_main_prev2 = 0.0
self._macd_signal_prev1 = 0.0
self._macd_signal_prev2 = 0.0
self._macd_samples = 0
self._prev_candle_open = 0.0
self._prev_candle_close = 0.0
self._has_prev_candle = False
self._adjusted_point = 0.0
self._stop_loss_distance = 0.0
self._breakeven_distance = 0.0
self._threshold_distance = 0.0
self._spacing_distance = 0.0
self._long_stop_price = 0.0
self._long_take_price = 0.0
self._long_breakeven_activated = False
self._long_entries = 0
self._last_long_signal_time = None
self._last_long_price = 0.0
self._short_stop_price = 0.0
self._short_take_price = 0.0
self._short_breakeven_activated = False
self._short_entries = 0
self._last_short_signal_time = None
self._last_short_price = 0.0
self._entry_price = 0.0
def CreateClone(self):
return anubis_strategy()