Kaufman Adaptive Moving Average Strategy
This strategy uses the Kaufman Adaptive Moving Average (KAMA). A long position is opened when KAMA rises for a specified number of bars and any short position is closed. A short position is opened when KAMA falls for the required period and any long position is closed.
Parameters
- Candle type
- Length
- Fast period
- Slow period
- Rising period
- Falling period
- Order direction
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>
/// Kaufman Adaptive Moving Average (KAMA) strategy.
/// </summary>
public class KaufmanAdaptiveMovingAverageStrategy : Strategy
{
public enum TradeSides
{
Long,
Short,
Both
}
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _fast;
private readonly StrategyParam<int> _slow;
private readonly StrategyParam<int> _risingPeriod;
private readonly StrategyParam<int> _fallingPeriod;
private readonly StrategyParam<TradeSides> _orderDirection;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevKama;
private int _risingCount;
private int _fallingCount;
private bool _isFirst = true;
private bool _wasRising;
private bool _wasFalling;
public int Length { get => _length.Value; set => _length.Value = value; }
public int Fast { get => _fast.Value; set => _fast.Value = value; }
public int Slow { get => _slow.Value; set => _slow.Value = value; }
public int RisingPeriod { get => _risingPeriod.Value; set => _risingPeriod.Value = value; }
public int FallingPeriod { get => _fallingPeriod.Value; set => _fallingPeriod.Value = value; }
public TradeSides OrderDirection { get => _orderDirection.Value; set => _orderDirection.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public KaufmanAdaptiveMovingAverageStrategy()
{
_length = Param(nameof(Length), 10)
.SetGreaterThanZero()
.SetDisplay("Length", "KAMA lookback period", "KAMA")
.SetOptimize(5, 20, 5);
_fast = Param(nameof(Fast), 5)
.SetGreaterThanZero()
.SetDisplay("Fast period", "Fast EMA length for KAMA", "KAMA")
.SetOptimize(2, 10, 1);
_slow = Param(nameof(Slow), 50)
.SetGreaterThanZero()
.SetDisplay("Slow period", "Slow EMA length for KAMA", "KAMA")
.SetOptimize(20, 100, 10);
_risingPeriod = Param(nameof(RisingPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Rising period", "Bars for KAMA rising condition", "Strategy")
.SetOptimize(5, 20, 5);
_fallingPeriod = Param(nameof(FallingPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Falling period", "Bars for KAMA falling condition", "Strategy")
.SetOptimize(5, 20, 5);
_orderDirection = Param(nameof(OrderDirection), TradeSides.Long)
.SetDisplay("Order direction", "Allowed trade direction", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevKama = default;
_risingCount = 0;
_fallingCount = 0;
_isFirst = true;
_wasRising = false;
_wasFalling = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var kama = new KaufmanAdaptiveMovingAverage
{
Length = Length,
FastSCPeriod = Fast,
SlowSCPeriod = Slow
};
var subscription = SubscribeCandles(CandleType);
subscription.Bind(kama, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, kama);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal kamaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_isFirst)
{
_prevKama = kamaValue;
_isFirst = false;
return;
}
if (kamaValue > _prevKama)
{
_risingCount++;
_fallingCount = 0;
}
else if (kamaValue < _prevKama)
{
_fallingCount++;
_risingCount = 0;
}
else
{
_risingCount = 0;
_fallingCount = 0;
}
var isRising = _risingCount >= RisingPeriod;
var isFalling = _fallingCount >= FallingPeriod;
var risingEdge = isRising && !_wasRising;
var fallingEdge = isFalling && !_wasFalling;
if (risingEdge)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
var allowLong = OrderDirection == TradeSides.Long || OrderDirection == TradeSides.Both;
if (allowLong && Position == 0)
BuyMarket(Volume);
}
if (fallingEdge)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
var allowShort = OrderDirection == TradeSides.Short || OrderDirection == TradeSides.Both;
if (allowShort && Position == 0)
SellMarket(Volume);
}
_wasRising = isRising;
_wasFalling = isFalling;
_prevKama = kamaValue;
}
}
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.Indicators import KaufmanAdaptiveMovingAverage
from StockSharp.Algo.Strategies import Strategy
class kaufman_adaptive_moving_average_strategy(Strategy):
def __init__(self):
super(kaufman_adaptive_moving_average_strategy, self).__init__()
self._length = self.Param("Length", 10) \
.SetGreaterThanZero() \
.SetDisplay("Length", "KAMA lookback period", "KAMA")
self._fast = self.Param("Fast", 5) \
.SetGreaterThanZero() \
.SetDisplay("Fast period", "Fast EMA length for KAMA", "KAMA")
self._slow = self.Param("Slow", 50) \
.SetGreaterThanZero() \
.SetDisplay("Slow period", "Slow EMA length for KAMA", "KAMA")
self._rising_period = self.Param("RisingPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Rising period", "Bars for KAMA rising condition", "Strategy")
self._falling_period = self.Param("FallingPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Falling period", "Bars for KAMA falling condition", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle type", "Type of candles", "General")
self._prev_kama = 0.0
self._rising_count = 0
self._falling_count = 0
self._is_first = True
self._was_rising = False
self._was_falling = False
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(kaufman_adaptive_moving_average_strategy, self).OnReseted()
self._prev_kama = 0.0
self._rising_count = 0
self._falling_count = 0
self._is_first = True
self._was_rising = False
self._was_falling = False
def OnStarted2(self, time):
super(kaufman_adaptive_moving_average_strategy, self).OnStarted2(time)
kama = KaufmanAdaptiveMovingAverage()
kama.Length = self._length.Value
kama.FastSCPeriod = self._fast.Value
kama.SlowSCPeriod = self._slow.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(kama, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, kama)
self.DrawOwnTrades(area)
def OnProcess(self, candle, kama_val):
if candle.State != CandleStates.Finished:
return
kv = float(kama_val)
if self._is_first:
self._prev_kama = kv
self._is_first = False
return
if kv > self._prev_kama:
self._rising_count += 1
self._falling_count = 0
elif kv < self._prev_kama:
self._falling_count += 1
self._rising_count = 0
else:
self._rising_count = 0
self._falling_count = 0
is_rising = self._rising_count >= self._rising_period.Value
is_falling = self._falling_count >= self._falling_period.Value
rising_edge = is_rising and not self._was_rising
falling_edge = is_falling and not self._was_falling
if rising_edge:
if self.Position < 0:
self.BuyMarket()
if self.Position == 0:
self.BuyMarket()
if falling_edge:
if self.Position > 0:
self.SellMarket()
if self.Position == 0:
self.SellMarket()
self._was_rising = is_rising
self._was_falling = is_falling
self._prev_kama = kv
def CreateClone(self):
return kaufman_adaptive_moving_average_strategy()