Blau C-Momentum 策略
概览
本策略是 MetaTrader 专家顾问 Exp_BlauCMomentum 的 StockSharp 移植版本。策略使用可配置时间框架的K线,并通过 Blau 三重平滑动量在两种模式下寻找信号:
- Breakdown(突破零线):当动量穿越零轴时寻找反向入场。
- Twist(转折):当动量斜率发生变化时捕捉趋势反转。
指标可以基于不同的应用价格计算,并能在开仓后自动设置止损/止盈。
工作流程
- 订阅所选时间框架的K线。
- 计算 Blau C-Momentum:
- 原始动量为两种应用价格在
MomentumLength根K线间的差值。 - 通过三次平滑(由
SmoothingMethod指定)并按点值缩放为 ×100/Point。
- 原始动量为两种应用价格在
- 保存动量历史,用于
SignalBar指定的回溯位移。 - 生成交易信号:
- Breakdown:前一根K线在零线上方且信号K线≤0 时做多;前一根K线在零线下方且信号K线≥0 时做空。可选的退出开关会在同一条件下关闭反向仓位。
- Twist:比较更早的两根K线。如果动量加速上行且信号K线确认,则做多;若动量加速下行则做空。
- 使用
MoneyManagement和MarginModes控制仓位大小。参数为负数时表示固定手数。策略会设置简单的时间锁,避免在同一根K线内重复入场。
主要参数
MoneyManagement:资金管理比例,负值代表固定手数。MarginModes:资金管理模式(按资金占比或按风险占比计算)。StopLossPoints/TakeProfitPoints:止损/止盈距离(单位为价格步长)。EnableLongEntry/EnableShortEntry:是否允许开多/开空。EnableLongExit/EnableShortExit:是否允许根据指标平仓。EntryModes:Breakdown或Twist模式。CandleType:指标计算使用的时间框架。SmoothingMethod:平滑方法(Simple、Exponential、Smoothed、LinearWeighted、Jurik、TripleExponential、Adaptive)。MomentumLength、FirstSmoothLength、SecondSmoothLength、ThirdSmoothLength、Phase:动量与平滑参数。PriceForClose/PriceForOpen:动量使用的应用价格。SignalBar:用于判断信号的K线索引(0 表示当前已完成的K线,1 表示上一根等)。
使用说明
- 在 StockSharp 中配置连接、投资组合与交易品种。
- 创建
BlauCMomentumStrategy实例,设置Security、Portfolio与参数。 - 调用
Start()后策略会自动订阅K线、计算指标并执行交易。 - 若
StopLossPoints/TakeProfitPoints大于0,策略会自动启用高阶 API 的保护模块。
注意事项
- StockSharp 中无法直接获取 MetaTrader 的余额/可用保证金,因此风险模式以
StopLossPoints和Security.StepPrice近似计算。 - 原版库中的 Parabolic、VIDYA、JurX 等平滑被映射为最接近的现有指标(如
TripleExponential≈ T3,Adaptive≈ KAMA)。 SlippagePoints仅为兼容而保留,策略始终使用市价单。
风险提示
本策略仅供学习交流,请在历史数据和模拟环境中充分验证后再用于真实交易,并根据自身风险承受能力调整仓位。
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Blau C-Momentum strategy converted from the MetaTrader expert advisor.
/// The strategy processes Blau's triple smoothed momentum and reacts either to zero breakouts or twists.
/// </summary>
public class BlauCMomentumStrategy : Strategy
{
private readonly StrategyParam<decimal> _moneyManagement;
private readonly StrategyParam<MarginModes> _marginMode;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _slippagePoints;
private readonly StrategyParam<bool> _enableLongEntry;
private readonly StrategyParam<bool> _enableShortEntry;
private readonly StrategyParam<bool> _enableLongExit;
private readonly StrategyParam<bool> _enableShortExit;
private readonly StrategyParam<EntryModes> _entryMode;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<SmoothMethods> _smoothingMethod;
private readonly StrategyParam<int> _momentumLength;
private readonly StrategyParam<int> _firstSmoothLength;
private readonly StrategyParam<int> _secondSmoothLength;
private readonly StrategyParam<int> _thirdSmoothLength;
private readonly StrategyParam<int> _phase;
private readonly StrategyParam<AppliedPrices> _priceForClose;
private readonly StrategyParam<AppliedPrices> _priceForOpen;
private readonly StrategyParam<int> _signalBar;
private BlauMomentumCalculator _momentum;
private readonly List<decimal> _indicatorHistory = new();
private TimeSpan _candleSpan;
private DateTimeOffset? _longTradeBlockUntil;
private DateTimeOffset? _shortTradeBlockUntil;
/// <summary>
/// Initializes a new instance of the <see cref="BlauCMomentumStrategy"/> class.
/// </summary>
public BlauCMomentumStrategy()
{
_moneyManagement = Param(nameof(MoneyManagement), 0.1m)
.SetDisplay("Money Management", "Fraction of capital used to size positions (negative value = fixed volume)", "Trading")
;
_marginMode = Param(nameof(MarginMode), MarginModes.FreeMarginShare)
.SetDisplay("Margin Mode", "Interpretation of money management parameter", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetDisplay("Stop Loss", "Stop loss distance in price steps", "Risk")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetDisplay("Take Profit", "Take profit distance in price steps", "Risk")
;
_slippagePoints = Param(nameof(SlippagePoints), 10)
.SetDisplay("Max Slippage", "Maximum slippage allowed in points", "Trading");
_enableLongEntry = Param(nameof(EnableLongEntry), true)
.SetDisplay("Enable Long Entry", "Allow opening long positions", "Trading");
_enableShortEntry = Param(nameof(EnableShortEntry), true)
.SetDisplay("Enable Short Entry", "Allow opening short positions", "Trading");
_enableLongExit = Param(nameof(EnableLongExit), true)
.SetDisplay("Enable Long Exit", "Allow closing long positions", "Trading");
_enableShortExit = Param(nameof(EnableShortExit), true)
.SetDisplay("Enable Short Exit", "Allow closing short positions", "Trading");
_entryMode = Param(nameof(EntryMode), EntryModes.Twist)
.SetDisplay("Entry Mode", "Choose between zero breakout or twist logic", "Logic");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Indicator Timeframe", "Candle type used for indicator calculations", "Data");
_smoothingMethod = Param(nameof(SmoothingMethod), SmoothMethods.Exponential)
.SetDisplay("Smoothing Method", "Smoothing method applied to the momentum", "Indicator")
;
_momentumLength = Param(nameof(MomentumLength), 1)
.SetGreaterThanZero()
.SetDisplay("Momentum Length", "Depth of raw momentum calculation", "Indicator")
;
_firstSmoothLength = Param(nameof(FirstSmoothLength), 20)
.SetGreaterThanZero()
.SetDisplay("First Smooth", "Length of the first smoothing stage", "Indicator")
;
_secondSmoothLength = Param(nameof(SecondSmoothLength), 5)
.SetGreaterThanZero()
.SetDisplay("Second Smooth", "Length of the second smoothing stage", "Indicator")
;
_thirdSmoothLength = Param(nameof(ThirdSmoothLength), 3)
.SetGreaterThanZero()
.SetDisplay("Third Smooth", "Length of the third smoothing stage", "Indicator")
;
_phase = Param(nameof(Phase), 15)
.SetDisplay("Phase", "Phase parameter used by Jurik-style moving averages", "Indicator");
_priceForClose = Param(nameof(PriceForClose), AppliedPrices.Close)
.SetDisplay("Close Price Source", "Applied price used as the reference close", "Indicator");
_priceForOpen = Param(nameof(PriceForOpen), AppliedPrices.Open)
.SetDisplay("Open Price Source", "Applied price used for the entry reference", "Indicator");
_signalBar = Param(nameof(SignalBar), 1)
.SetDisplay("Signal Bar", "Bar index used for generating entry signals", "Logic")
;
}
/// <summary>
/// Fraction of capital (or fixed lot size) used for trading.
/// </summary>
public decimal MoneyManagement
{
get => _moneyManagement.Value;
set => _moneyManagement.Value = value;
}
/// <summary>
/// Interpretation of the money management parameter.
/// </summary>
public MarginModes MarginMode
{
get => _marginMode.Value;
set => _marginMode.Value = value;
}
/// <summary>
/// Stop loss distance expressed in instrument price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in instrument price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Maximum tolerated slippage in points.
/// </summary>
public int SlippagePoints
{
get => _slippagePoints.Value;
set => _slippagePoints.Value = value;
}
/// <summary>
/// Enable opening long positions.
/// </summary>
public bool EnableLongEntry
{
get => _enableLongEntry.Value;
set => _enableLongEntry.Value = value;
}
/// <summary>
/// Enable opening short positions.
/// </summary>
public bool EnableShortEntry
{
get => _enableShortEntry.Value;
set => _enableShortEntry.Value = value;
}
/// <summary>
/// Enable closing long positions on indicator signals.
/// </summary>
public bool EnableLongExit
{
get => _enableLongExit.Value;
set => _enableLongExit.Value = value;
}
/// <summary>
/// Enable closing short positions on indicator signals.
/// </summary>
public bool EnableShortExit
{
get => _enableShortExit.Value;
set => _enableShortExit.Value = value;
}
/// <summary>
/// Entry logic: zero-line breakdown or twist detection.
/// </summary>
public EntryModes EntryMode
{
get => _entryMode.Value;
set => _entryMode.Value = value;
}
/// <summary>
/// Candle type used to drive the indicator.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Smoothing method applied to Blau momentum.
/// </summary>
public SmoothMethods SmoothingMethod
{
get => _smoothingMethod.Value;
set => _smoothingMethod.Value = value;
}
/// <summary>
/// Momentum averaging depth.
/// </summary>
public int MomentumLength
{
get => _momentumLength.Value;
set => _momentumLength.Value = value;
}
/// <summary>
/// First smoothing stage length.
/// </summary>
public int FirstSmoothLength
{
get => _firstSmoothLength.Value;
set => _firstSmoothLength.Value = value;
}
/// <summary>
/// Second smoothing stage length.
/// </summary>
public int SecondSmoothLength
{
get => _secondSmoothLength.Value;
set => _secondSmoothLength.Value = value;
}
/// <summary>
/// Third smoothing stage length.
/// </summary>
public int ThirdSmoothLength
{
get => _thirdSmoothLength.Value;
set => _thirdSmoothLength.Value = value;
}
/// <summary>
/// Phase parameter used by Jurik-styled smoothing.
/// </summary>
public int Phase
{
get => _phase.Value;
set => _phase.Value = value;
}
/// <summary>
/// Applied price for the "closing" component.
/// </summary>
public AppliedPrices PriceForClose
{
get => _priceForClose.Value;
set => _priceForClose.Value = value;
}
/// <summary>
/// Applied price for the "opening" component.
/// </summary>
public AppliedPrices PriceForOpen
{
get => _priceForOpen.Value;
set => _priceForOpen.Value = value;
}
/// <summary>
/// Index of the bar used for generating entry signals.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_indicatorHistory.Clear();
_momentum = null;
_longTradeBlockUntil = null;
_shortTradeBlockUntil = null;
_candleSpan = TimeSpan.Zero;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_indicatorHistory.Clear();
_momentum = new BlauMomentumCalculator(
SmoothingMethod,
MomentumLength,
FirstSmoothLength,
SecondSmoothLength,
ThirdSmoothLength,
Phase,
PriceForClose,
PriceForOpen
);
_candleSpan = CandleType.Arg is TimeSpan frame ? frame : TimeSpan.Zero;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 0m;
var takeProfitUnit = TakeProfitPoints > 0 && step > 0m ? new Unit(TakeProfitPoints * step, UnitTypes.Absolute) : null;
var stopLossUnit = StopLossPoints > 0 && step > 0m ? new Unit(StopLossPoints * step, UnitTypes.Absolute) : null;
StartProtection(takeProfitUnit, stopLossUnit);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || _momentum is null)
return;
var step = Security?.PriceStep ?? 1m;
var indicatorValue = _momentum.Process(candle, step);
if (indicatorValue is null)
return;
_indicatorHistory.Add(indicatorValue.Value);
var requiredHistory = Math.Max(SignalBar + 3, 5);
if (_indicatorHistory.Count > requiredHistory)
_indicatorHistory.RemoveRange(0, _indicatorHistory.Count - requiredHistory);
// indicators are checked via history availability below
var current = GetHistoryValue(SignalBar);
var previous = GetHistoryValue(SignalBar + 1);
if (current is null || previous is null)
return;
var closeShort = false;
var closeLong = false;
var openLong = false;
var openShort = false;
switch (EntryMode)
{
case EntryModes.Breakdown:
{
if (previous.Value > 0m)
{
if (EnableLongEntry && current.Value <= 0m)
{
openLong = true;
}
if (EnableShortExit)
{
closeShort = true;
}
}
if (previous.Value < 0m)
{
if (EnableShortEntry && current.Value >= 0m)
{
openShort = true;
}
if (EnableLongExit)
{
closeLong = true;
}
}
break;
}
case EntryModes.Twist:
{
var older = GetHistoryValue(SignalBar + 2);
if (older is null)
return;
if (previous.Value < older.Value)
{
if (EnableLongEntry && current.Value >= previous.Value)
{
openLong = true;
}
if (EnableShortExit)
{
closeShort = true;
}
}
if (previous.Value > older.Value)
{
if (EnableShortEntry && current.Value <= previous.Value)
{
openShort = true;
}
if (EnableLongExit)
{
closeLong = true;
}
}
break;
}
}
if (closeLong && Position > 0m)
{
SellMarket();
}
if (closeShort && Position < 0m)
{
BuyMarket();
}
if (openLong && Position <= 0m && CanEnterLong(candle.OpenTime))
{
BuyMarket();
SetLongBlock(candle.OpenTime);
}
if (openShort && Position >= 0m && CanEnterShort(candle.OpenTime))
{
SellMarket();
SetShortBlock(candle.OpenTime);
}
}
private decimal? GetHistoryValue(int shift)
{
if (shift < 0)
return null;
var index = _indicatorHistory.Count - shift - 1;
if (index < 0 || index >= _indicatorHistory.Count)
return null;
return _indicatorHistory[index];
}
private bool CanEnterLong(DateTimeOffset signalTime)
{
return !_longTradeBlockUntil.HasValue || signalTime >= _longTradeBlockUntil.Value;
}
private bool CanEnterShort(DateTimeOffset signalTime)
{
return !_shortTradeBlockUntil.HasValue || signalTime >= _shortTradeBlockUntil.Value;
}
private void SetLongBlock(DateTimeOffset signalTime)
{
_longTradeBlockUntil = _candleSpan != TimeSpan.Zero ? signalTime + _candleSpan : signalTime;
}
private void SetShortBlock(DateTimeOffset signalTime)
{
_shortTradeBlockUntil = _candleSpan != TimeSpan.Zero ? signalTime + _candleSpan : signalTime;
}
private decimal CalculateTradeVolume(decimal price)
{
if (price <= 0m)
return 0m;
var step = Security?.VolumeStep ?? 1m;
var minVolume = Security?.MinVolume ?? step;
var capital = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
var moneyManagement = MoneyManagement;
decimal volume;
if (moneyManagement < 0m)
{
volume = Math.Abs(moneyManagement);
}
else
{
if (capital <= 0m)
return minVolume;
switch (MarginMode)
{
case MarginModes.FreeMarginShare:
case MarginModes.BalanceShare:
{
var budget = capital * moneyManagement;
volume = budget / price;
break;
}
case MarginModes.FreeMarginRisk:
case MarginModes.BalanceRisk:
{
var riskCapital = capital * moneyManagement;
var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 1m;
var stopLoss = StopLossPoints > 0 ? StopLossPoints * stepPrice : price;
volume = stopLoss > 0m ? riskCapital / stopLoss : riskCapital / price;
break;
}
default:
{
var budget = capital * moneyManagement;
volume = budget / price;
break;
}
}
}
if (step > 0m && volume > 0m)
{
volume = Math.Floor(volume / step) * step;
}
if (volume < minVolume)
volume = minVolume;
return volume;
}
/// <summary>
/// Entry mode replication.
/// </summary>
public enum EntryModes
{
/// <summary>
/// Entry when the indicator breaks zero.
/// </summary>
Breakdown,
/// <summary>
/// Entry when the indicator changes direction (twist).
/// </summary>
Twist
}
/// <summary>
/// Applied price selection.
/// </summary>
public enum AppliedPrices
{
/// <summary>
/// Closing price.
/// </summary>
Close = 1,
/// <summary>
/// Opening price.
/// </summary>
Open,
/// <summary>
/// High price.
/// </summary>
High,
/// <summary>
/// Low price.
/// </summary>
Low,
/// <summary>
/// Median price (HL/2).
/// </summary>
Median,
/// <summary>
/// Typical price (HLC/3).
/// </summary>
Typical,
/// <summary>
/// Weighted close (HLCC/4).
/// </summary>
Weighted,
/// <summary>
/// Simple price (OC/2).
/// </summary>
Simple,
/// <summary>
/// Quarted price (HLOC/4).
/// </summary>
Quarter,
/// <summary>
/// Trend-following price variant 1.
/// </summary>
TrendFollow1,
/// <summary>
/// Trend-following price variant 2.
/// </summary>
TrendFollow2,
/// <summary>
/// Demark price.
/// </summary>
Demark
}
/// <summary>
/// Money management interpretation.
/// </summary>
public enum MarginModes
{
/// <summary>
/// Use a fraction of account capital (approximation of free margin share).
/// </summary>
FreeMarginShare = 0,
/// <summary>
/// Use a fraction of balance (treated equally to free margin share in this port).
/// </summary>
BalanceShare = 1,
/// <summary>
/// Risk a fraction of capital with stop-loss distance.
/// </summary>
FreeMarginRisk = 2,
/// <summary>
/// Risk a fraction of balance with stop-loss distance.
/// </summary>
BalanceRisk = 3
}
/// <summary>
/// Smoothing methods available for Blau momentum.
/// </summary>
public enum SmoothMethods
{
/// <summary>
/// Simple moving average.
/// </summary>
Simple,
/// <summary>
/// Exponential moving average.
/// </summary>
Exponential,
/// <summary>
/// Smoothed moving average (RMA/SMMA).
/// </summary>
Smoothed,
/// <summary>
/// Linear weighted moving average.
/// </summary>
LinearWeighted,
/// <summary>
/// Jurik moving average.
/// </summary>
Jurik,
/// <summary>
/// Triple exponential moving average (approximation of T3).
/// </summary>
TripleExponential,
/// <summary>
/// Kaufman adaptive moving average.
/// </summary>
Adaptive
}
private sealed class BlauMomentumCalculator
{
private readonly SmoothMethods _method;
private readonly int _momentumLength;
private readonly int _firstLength;
private readonly int _secondLength;
private readonly int _thirdLength;
private readonly int _phase;
private readonly AppliedPrices _price1;
private readonly AppliedPrices _price2;
private readonly List<decimal> _priceBuffer = new();
private readonly DecimalLengthIndicator _ma1;
private readonly DecimalLengthIndicator _ma2;
private readonly DecimalLengthIndicator _ma3;
public BlauMomentumCalculator(
SmoothMethods method,
int momentumLength,
int firstLength,
int secondLength,
int thirdLength,
int phase,
AppliedPrices price1,
AppliedPrices price2)
{
_method = method;
_momentumLength = Math.Max(1, momentumLength);
_firstLength = Math.Max(1, firstLength);
_secondLength = Math.Max(1, secondLength);
_thirdLength = Math.Max(1, thirdLength);
_phase = phase;
_price1 = price1;
_price2 = price2;
_ma1 = CreateMovingAverage(method, _firstLength, _phase);
_ma2 = CreateMovingAverage(method, _secondLength, _phase);
_ma3 = CreateMovingAverage(method, _thirdLength, _phase);
}
public decimal? Process(ICandleMessage candle, decimal point)
{
var value1 = GetAppliedPrice(_price1, candle);
var value2 = GetAppliedPrice(_price2, candle);
_priceBuffer.Add(value2);
if (_priceBuffer.Count > _momentumLength)
try { _priceBuffer.RemoveAt(0); } catch { }
if (_priceBuffer.Count < _momentumLength)
return null;
var reference = _priceBuffer[0];
var momentum = value1 - reference;
var time = candle.OpenTime;
var smooth1Result = _ma1.Process(new DecimalIndicatorValue(_ma1, momentum, time) { IsFinal = true });
if (!_ma1.IsFormed)
return null;
var smooth1 = smooth1Result.ToDecimal();
var smooth2Result = _ma2.Process(new DecimalIndicatorValue(_ma2, smooth1, time) { IsFinal = true });
if (!_ma2.IsFormed)
return null;
var smooth2 = smooth2Result.ToDecimal();
var smooth3Result = _ma3.Process(new DecimalIndicatorValue(_ma3, smooth2, time) { IsFinal = true });
if (!_ma3.IsFormed)
return null;
var smooth3 = smooth3Result.ToDecimal();
return point > 0m ? smooth3 * 100m / point : smooth3;
}
public void Reset()
{
_priceBuffer.Clear();
_ma1.Reset();
_ma2.Reset();
_ma3.Reset();
}
private static DecimalLengthIndicator CreateMovingAverage(SmoothMethods method, int length, int phase)
{
return method switch
{
SmoothMethods.Simple => new SMA { Length = length },
SmoothMethods.Exponential => new EMA { Length = length },
SmoothMethods.Smoothed => new SmoothedMovingAverage { Length = length },
SmoothMethods.LinearWeighted => new WeightedMovingAverage { Length = length },
SmoothMethods.Jurik => new JurikMovingAverage { Length = length, Phase = phase },
SmoothMethods.TripleExponential => new TripleExponentialMovingAverage { Length = length },
SmoothMethods.Adaptive => new KaufmanAdaptiveMovingAverage { Length = length },
_ => new EMA { Length = length }
};
}
private static decimal GetAppliedPrice(AppliedPrices price, ICandleMessage candle)
{
return price switch
{
AppliedPrices.Close => candle.ClosePrice,
AppliedPrices.Open => candle.OpenPrice,
AppliedPrices.High => candle.HighPrice,
AppliedPrices.Low => candle.LowPrice,
AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPrices.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
AppliedPrices.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
AppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice : candle.ClosePrice < candle.OpenPrice ? candle.LowPrice : candle.ClosePrice,
AppliedPrices.TrendFollow2 => candle.ClosePrice > candle.OpenPrice ? (candle.HighPrice + candle.ClosePrice) / 2m : candle.ClosePrice < candle.OpenPrice ? (candle.LowPrice + candle.ClosePrice) / 2m : candle.ClosePrice,
AppliedPrices.Demark => CalculateDemarkPrice(candle),
_ => candle.ClosePrice
};
}
private static decimal CalculateDemarkPrice(ICandleMessage candle)
{
var res = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
if (candle.ClosePrice < candle.OpenPrice)
res = (res + candle.LowPrice) / 2m;
else if (candle.ClosePrice > candle.OpenPrice)
res = (res + candle.HighPrice) / 2m;
else
res = (res + candle.ClosePrice) / 2m;
return ((res - candle.LowPrice) + (res - candle.HighPrice)) / 2m;
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import (
SimpleMovingAverage, ExponentialMovingAverage, SmoothedMovingAverage,
WeightedMovingAverage, JurikMovingAverage, TripleExponentialMovingAverage,
KaufmanAdaptiveMovingAverage
)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class blau_c_momentum_strategy(Strategy):
def __init__(self):
super(blau_c_momentum_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._enable_long_entry = self.Param("EnableLongEntry", True)
self._enable_short_entry = self.Param("EnableShortEntry", True)
self._enable_long_exit = self.Param("EnableLongExit", True)
self._enable_short_exit = self.Param("EnableShortExit", True)
self._entry_mode = self.Param("EntryMode", 1)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._smoothing_method = self.Param("SmoothingMethod", 1)
self._momentum_length = self.Param("MomentumLength", 1)
self._first_smooth_length = self.Param("FirstSmoothLength", 20)
self._second_smooth_length = self.Param("SecondSmoothLength", 5)
self._third_smooth_length = self.Param("ThirdSmoothLength", 3)
self._phase = self.Param("Phase", 15)
self._price_for_close = self.Param("PriceForClose", 1)
self._price_for_open = self.Param("PriceForOpen", 2)
self._signal_bar = self.Param("SignalBar", 1)
self._indicator_history = []
self._momentum_calc = None
self._candle_span = 0
self._long_trade_block_until = None
self._short_trade_block_until = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def EnableLongEntry(self):
return self._enable_long_entry.Value
@property
def EnableShortEntry(self):
return self._enable_short_entry.Value
@property
def EnableLongExit(self):
return self._enable_long_exit.Value
@property
def EnableShortExit(self):
return self._enable_short_exit.Value
@property
def EntryMode(self):
return self._entry_mode.Value
@property
def SmoothingMethod(self):
return self._smoothing_method.Value
@property
def MomentumLength(self):
return self._momentum_length.Value
@property
def FirstSmoothLength(self):
return self._first_smooth_length.Value
@property
def SecondSmoothLength(self):
return self._second_smooth_length.Value
@property
def ThirdSmoothLength(self):
return self._third_smooth_length.Value
@property
def Phase(self):
return self._phase.Value
@property
def PriceForClose(self):
return self._price_for_close.Value
@property
def PriceForOpen(self):
return self._price_for_open.Value
@property
def SignalBar(self):
return self._signal_bar.Value
def OnStarted2(self, time):
super(blau_c_momentum_strategy, self).OnStarted2(time)
self._indicator_history = []
self._momentum_calc = _BlauMomentumCalc(
self.SmoothingMethod, self.MomentumLength, self.FirstSmoothLength,
self.SecondSmoothLength, self.ThirdSmoothLength, self.Phase,
self.PriceForClose, self.PriceForOpen
)
try:
ct = self.CandleType
self._candle_span = ct.Arg.TotalSeconds if ct.Arg is not None else 0
except:
self._candle_span = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0
tp_unit = Unit(self.TakeProfitPoints * step, UnitTypes.Absolute) if self.TakeProfitPoints > 0 and step > 0 else None
sl_unit = Unit(self.StopLossPoints * step, UnitTypes.Absolute) if self.StopLossPoints > 0 and step > 0 else None
self.StartProtection(tp_unit, sl_unit)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished or self._momentum_calc is None:
return
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
val = self._momentum_calc.process(candle, step)
if val is None:
return
self._indicator_history.append(val)
required = max(self.SignalBar + 3, 5)
if len(self._indicator_history) > required:
self._indicator_history = self._indicator_history[-required:]
current = self._get_hist(self.SignalBar)
previous = self._get_hist(self.SignalBar + 1)
if current is None or previous is None:
return
close_short = False
close_long = False
open_long = False
open_short = False
if self.EntryMode == 0: # Breakdown
if previous > 0:
if self.EnableLongEntry and current <= 0:
open_long = True
if self.EnableShortExit:
close_short = True
if previous < 0:
if self.EnableShortEntry and current >= 0:
open_short = True
if self.EnableLongExit:
close_long = True
else: # Twist
older = self._get_hist(self.SignalBar + 2)
if older is None:
return
if previous < older:
if self.EnableLongEntry and current >= previous:
open_long = True
if self.EnableShortExit:
close_short = True
if previous > older:
if self.EnableShortEntry and current <= previous:
open_short = True
if self.EnableLongExit:
close_long = True
if close_long and self.Position > 0:
self.SellMarket()
if close_short and self.Position < 0:
self.BuyMarket()
if open_long and self.Position <= 0 and self._can_enter_long(candle.OpenTime):
self.BuyMarket()
self._set_block(True, candle.OpenTime)
if open_short and self.Position >= 0 and self._can_enter_short(candle.OpenTime):
self.SellMarket()
self._set_block(False, candle.OpenTime)
def _get_hist(self, shift):
if shift < 0:
return None
idx = len(self._indicator_history) - shift - 1
if idx < 0 or idx >= len(self._indicator_history):
return None
return self._indicator_history[idx]
def _can_enter_long(self, t):
return self._long_trade_block_until is None or t >= self._long_trade_block_until
def _can_enter_short(self, t):
return self._short_trade_block_until is None or t >= self._short_trade_block_until
def _set_block(self, is_long, t):
blocked = t.AddSeconds(self._candle_span) if self._candle_span > 0 else t
if is_long:
self._long_trade_block_until = blocked
else:
self._short_trade_block_until = blocked
def OnReseted(self):
super(blau_c_momentum_strategy, self).OnReseted()
self._indicator_history = []
self._momentum_calc = None
self._long_trade_block_until = None
self._short_trade_block_until = None
self._candle_span = 0
def CreateClone(self):
return blau_c_momentum_strategy()
class _BlauMomentumCalc:
def __init__(self, method, mom_len, s1_len, s2_len, s3_len, phase, price1, price2):
self._mom_len = max(1, mom_len)
self._price1 = price1
self._price2 = price2
self._buf = []
self._ma1 = self._make_ma(method, max(1, s1_len), phase)
self._ma2 = self._make_ma(method, max(1, s2_len), phase)
self._ma3 = self._make_ma(method, max(1, s3_len), phase)
def process(self, candle, point):
v1 = self._price(self._price1, candle)
v2 = self._price(self._price2, candle)
self._buf.append(v2)
if len(self._buf) > self._mom_len:
self._buf.pop(0)
if len(self._buf) < self._mom_len:
return None
momentum = v1 - self._buf[0]
t = candle.ServerTime
r1 = process_float(self._ma1, Decimal(momentum), t, True)
if not self._ma1.IsFormed:
return None
s1 = float(r1.Value)
r2 = process_float(self._ma2, Decimal(s1), t, True)
if not self._ma2.IsFormed:
return None
s2 = float(r2.Value)
r3 = process_float(self._ma3, Decimal(s2), t, True)
if not self._ma3.IsFormed:
return None
s3 = float(r3.Value)
return s3 * 100.0 / point if point > 0 else s3
def _make_ma(self, method, length, phase):
if method == 0:
m = SimpleMovingAverage(); m.Length = length; return m
elif method == 1:
m = ExponentialMovingAverage(); m.Length = length; return m
elif method == 2:
m = SmoothedMovingAverage(); m.Length = length; return m
elif method == 3:
m = WeightedMovingAverage(); m.Length = length; return m
elif method == 4:
m = JurikMovingAverage(); m.Length = length; m.Phase = phase; return m
elif method == 5:
m = TripleExponentialMovingAverage(); m.Length = length; return m
elif method == 6:
m = KaufmanAdaptiveMovingAverage(); m.Length = length; return m
else:
m = ExponentialMovingAverage(); m.Length = length; return m
def _price(self, pt, c):
cl = float(c.ClosePrice); op = float(c.OpenPrice)
hi = float(c.HighPrice); lo = float(c.LowPrice)
if pt == 1: return cl
elif pt == 2: return op
elif pt == 3: return hi
elif pt == 4: return lo
elif pt == 5: return (hi + lo) / 2.0
elif pt == 6: return (cl + hi + lo) / 3.0
elif pt == 7: return (2.0 * cl + hi + lo) / 4.0
elif pt == 8: return (op + cl) / 2.0
elif pt == 9: return (op + cl + hi + lo) / 4.0
elif pt == 10:
return hi if cl > op else (lo if cl < op else cl)
elif pt == 11:
return (hi + cl) / 2.0 if cl > op else ((lo + cl) / 2.0 if cl < op else cl)
elif pt == 12:
r = hi + lo + cl
if cl < op: r = (r + lo) / 2.0
elif cl > op: r = (r + hi) / 2.0
else: r = (r + cl) / 2.0
return ((r - lo) + (r - hi)) / 2.0
else: return cl