Beta Weighted MA Strategy
The Beta Weighted MA (BWMA) strategy uses a Beta distribution to weight recent prices, producing a moving average whose lag and smoothness can be tuned with alpha and beta parameters. The strategy enters a long position when price crosses above the BWMA and a short position when price crosses below it.
Details
- Entry Criteria:
- Price crosses above the Beta Weighted Moving Average → enter long.
- Price crosses below the Beta Weighted Moving Average → enter short.
- Long/Short: Both.
- Exit Criteria:
- Opposite cross closes the current position and opens the reverse.
- Stops: None.
- Default Values:
Length= 50Alpha= 3Beta= 3
- Filters:
- Category: Trend following
- Direction: Long/Short
- Indicators: Beta Weighted Moving Average
- Stops: No
- Complexity: Low
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Beta Weighted Moving Average (BWMA).
/// Buys when price crosses above BWMA and sells when crosses below.
/// </summary>
public class BetaWeightedMaStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _alpha;
private readonly StrategyParam<decimal> _beta;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _prices = [];
private readonly List<decimal> _weights = [];
private decimal _denominator;
private decimal _prevMa;
private decimal _prevPrice;
private int _cooldownRemaining;
/// <summary>
/// Period for BWMA calculation.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Alpha (+Lag) parameter.
/// </summary>
public decimal Alpha
{
get => _alpha.Value;
set => _alpha.Value = value;
}
/// <summary>
/// Beta (-Lag) parameter.
/// </summary>
public decimal Beta
{
get => _beta.Value;
set => _beta.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize <see cref="BetaWeightedMaStrategy"/>.
/// </summary>
public BetaWeightedMaStrategy()
{
_length = Param(nameof(Length), 50)
.SetDisplay("BWMA Length", "Number of periods for Beta Weighted MA", "Parameters")
.SetOptimize(10, 100, 10);
_alpha = Param(nameof(Alpha), 3m)
.SetDisplay("Alpha (+Lag)", "Alpha parameter for Beta weighting", "Parameters")
.SetOptimize(1m, 10m, 1m);
_beta = Param(nameof(Beta), 3m)
.SetDisplay("Beta (-Lag)", "Beta parameter for Beta weighting", "Parameters")
.SetOptimize(1m, 10m, 1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_weights.Clear();
_denominator = default;
_prevMa = default;
_prevPrice = default;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Precompute weights based on Beta distribution
_weights.Clear();
_denominator = 0m;
var alpha = (double)Alpha;
var beta = (double)Beta;
for (var i = 0; i < Length; i++)
{
var x = (double)i / (Length - 1);
var w = Math.Pow(x, alpha - 1) * Math.Pow(1 - x, beta - 1);
var wd = (decimal)w;
_weights.Add(wd);
_denominator += wd;
}
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
// cooldown init
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
_prices.Insert(0, candle.ClosePrice);
if (_prices.Count > Length)
_prices.RemoveAt(_prices.Count - 1);
if (_prices.Count < Length)
return;
decimal sum = 0m;
for (var i = 0; i < Length; i++)
sum += _prices[i] * _weights[i];
var ma = sum / _denominator;
if (_prevMa == 0m)
{
_prevMa = ma;
_prevPrice = candle.ClosePrice;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevPrice = candle.ClosePrice;
_prevMa = ma;
return;
}
var crossedAbove = candle.ClosePrice > ma && _prevPrice <= _prevMa;
var crossedBelow = candle.ClosePrice < ma && _prevPrice >= _prevMa;
if (crossedAbove && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
else if (crossedBelow && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
_prevPrice = candle.ClosePrice;
_prevMa = ma;
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class beta_weighted_ma_strategy(Strategy):
"""Beta Weighted MA Strategy."""
def __init__(self):
super(beta_weighted_ma_strategy, self).__init__()
self._length = self.Param("Length", 50) \
.SetDisplay("BWMA Length", "Number of periods for Beta Weighted MA", "Parameters")
self._alpha = self.Param("Alpha", 3.0) \
.SetDisplay("Alpha (+Lag)", "Alpha parameter for Beta weighting", "Parameters")
self._beta_param = self.Param("Beta", 3.0) \
.SetDisplay("Beta (-Lag)", "Beta parameter for Beta weighting", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._prices = []
self._weights = []
self._denominator = 0.0
self._prev_ma = 0.0
self._prev_price = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(beta_weighted_ma_strategy, self).OnReseted()
self._prices = []
self._weights = []
self._denominator = 0.0
self._prev_ma = 0.0
self._prev_price = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(beta_weighted_ma_strategy, self).OnStarted2(time)
length = int(self._length.Value)
alpha = float(self._alpha.Value)
beta = float(self._beta_param.Value)
self._weights = []
self._denominator = 0.0
for i in range(length):
x = float(i) / (length - 1)
w = math.pow(x, alpha - 1) * math.pow(1 - x, beta - 1)
self._weights.append(w)
self._denominator += w
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._on_process).Start()
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
length = int(self._length.Value)
self._prices.insert(0, float(candle.ClosePrice))
if len(self._prices) > length:
self._prices.pop()
if len(self._prices) < length:
return
total = 0.0
for i in range(length):
total += self._prices[i] * self._weights[i]
ma = total / self._denominator if self._denominator != 0 else 0.0
if self._prev_ma == 0.0:
self._prev_ma = ma
self._prev_price = float(candle.ClosePrice)
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_price = float(candle.ClosePrice)
self._prev_ma = ma
return
close = float(candle.ClosePrice)
cooldown = int(self._cooldown_bars.Value)
crossed_above = close > ma and self._prev_price <= self._prev_ma
crossed_below = close < ma and self._prev_price >= self._prev_ma
if crossed_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif crossed_below and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._prev_price = close
self._prev_ma = ma
def CreateClone(self):
return beta_weighted_ma_strategy()