Blau C-Momentum Strategy
Overview
This strategy is a StockSharp port of the MetaTrader expert advisor Exp_BlauCMomentum. It trades on a single instrument using candles from a configurable timeframe and interprets Blau's triple-smoothed momentum in one of two modes:
- Breakdown mode – reacts to the momentum line crossing the zero level.
- Twist mode – reacts to changes in the direction of the smoothed momentum slope.
The indicator is calculated on an external timeframe and can optionally use different applied prices for the momentum calculation. Positions are opened with market orders and can be protected using built-in stop-loss and take-profit modules.
How it works
- Subscribe to candles of the selected timeframe.
- Compute Blau C-Momentum:
- Raw momentum is the difference between two applied prices separated by
MomentumLengthbars. - The raw momentum is smoothed three times by the chosen moving-average method and scaled to price steps (×100/Point).
- Raw momentum is the difference between two applied prices separated by
- Store the smoothed indicator history for bar shifts defined by
SignalBar. - Generate signals:
- Breakdown – if the previous bar was above zero and the signal bar is below or equal to zero, open/flip long; if the previous bar was below zero and the signal bar is above or equal to zero, open/flip short. Optional exit flags close the opposite side when the previous bar crosses the zero line.
- Twist – compare two previous bars; when momentum accelerates upward (previous < older) and the signal bar confirms, open/flip long; when momentum accelerates downward (previous > older) and the signal bar confirms, open/flip short. Optional exit flags close the opposite side on the same condition.
- Use
MoneyManagementandMarginModesto size the position. Negative values mean fixed volume; positive values risk or allocate a fraction of the portfolio value. A simple time lock prevents immediate re-entries within the same candle.
Parameters
| Group | Name | Description |
|---|---|---|
| Trading | MoneyManagement |
Share of capital for position sizing. Negative value = fixed volume. |
| Trading | MarginModes |
Interpretation of money management (FreeMarginShare, BalanceShare, FreeMarginRisk, BalanceRisk). Risk modes use stop-loss distance and StepPrice. |
| Risk | StopLossPoints |
Stop-loss distance in instrument price steps (set 0 to disable). |
| Risk | TakeProfitPoints |
Take-profit distance in instrument price steps (set 0 to disable). |
| Trading | SlippagePoints |
Allowed slippage (kept for compatibility, not used for order placement). |
| Trading | EnableLongEntry, EnableShortEntry |
Allow opening long/short positions. |
| Trading | EnableLongExit, EnableShortExit |
Allow closing existing positions according to the indicator. |
| Logic | EntryModes |
Breakdown or Twist. |
| Data | CandleType |
Timeframe used for indicator calculations (default 4h). |
| Indicator | SmoothingMethod |
Moving-average method: Simple, Exponential, Smoothed, LinearWeighted, Jurik, TripleExponential, Adaptive. |
| Indicator | MomentumLength |
Raw momentum averaging depth (bars between the two price values). |
| Indicator | FirstSmoothLength, SecondSmoothLength, ThirdSmoothLength |
Lengths of the three smoothing stages. |
| Indicator | Phase |
Jurik phase parameter (used when smoothing method is Jurik). |
| Indicator | PriceForClose, PriceForOpen |
Applied prices used for momentum (see code comments for formulas). |
| Logic | SignalBar |
Bar index used for signals (0 = current closed bar, 1 = previous bar, etc.). |
Usage notes
- Attach the strategy to a security and configure the candle series. The trading timeframe is the same as the indicator timeframe.
- The high-level API protection module is enabled automatically when stop/take profit values are positive.
- Margin modes are approximations because StockSharp does not expose MetaTrader-style balance/free margin. Risk-based modes rely on
StopLossPointsandSecurity.StepPrice. - Advanced smoothing methods from the original library (Parabolic, VIDYA, JurX) are mapped to the closest available StockSharp indicators (
TripleExponential≈ T3,Adaptive≈ KAMA). - Slippage parameter is preserved for completeness but market orders are used, so the value is informational.
Getting started
- Configure connection, portfolio, and security in your StockSharp environment.
- Create an instance of
BlauCMomentumStrategy, assignSecurity,Portfolio, and desired parameters. - Call
Start(); the strategy will subscribe to candles, calculate the indicator, and trade automatically. - Monitor logs for information about opened/closed positions and indicator states.
Risk disclaimer
This strategy is provided for educational purposes. Always validate performance with historical and forward tests before running it on a live account. Adjust risk settings to match your capital and market conditions.
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