Flexible Gleitender Durchschnitt-Strategie
Passt die Position basierend auf Kreuzungen zwischen dem Schlusskurs der vorherigen Periode und einem konfigurierbaren gleitenden Durchschnitt an. Ein Kreuzung nach unten reduziert die Position um einen benutzerdefinierten Prozentsatz, während eine Kreuzung nach oben die volle Position wiederherstellt.
Details
- Einstiegskriterien:
- Initial: Optionaler vollständiger Long auf der ersten Kerze.
- Erhöhung: Vorheriger Schlusskurs kreuzt über den gleitenden Durchschnitt → Position auf 100%.
- Ausstiegskriterien:
- Reduzierung: Vorheriger Schlusskurs kreuzt unter den gleitenden Durchschnitt → Reduzierung um
SellPercentage.
- Reduzierung: Vorheriger Schlusskurs kreuzt unter den gleitenden Durchschnitt → Reduzierung um
- Indikatoren:
- Einfacher, exponentieller, gewichteter, Hull- oder geglätteter gleitender Durchschnitt.
- Stops: Keine.
- Standardwerte:
MaLength= 200SellPercentage= 100MaMethod= SMAAllowInitialBuy= true
- Filter:
- Trendfolge
- Einzelner Zeitrahmen
- Indikatoren: Gleitende Durchschnitte
- Stops: keine
- Komplexität: Grundlegend
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>
/// Flexible Moving Average strategy.
/// A long-only position is sized against a moving average of the chosen method. With AllowInitialBuy the full position is bought
/// on the first tradable candle. When a candle closes above the average after closing at or below it, the position is restored
/// to the full Volume; when it closes below after closing at or above, the position is reduced by SellPercentage percent.
/// </summary>
public class FlexibleMovingAverageStrategy : Strategy
{
/// <summary>
/// Moving average methods.
/// </summary>
public enum MaMethods
{
/// <summary>
/// Simple moving average.
/// </summary>
SMA,
/// <summary>
/// Exponential moving average.
/// </summary>
EMA,
/// <summary>
/// Weighted moving average.
/// </summary>
WMA,
/// <summary>
/// Hull moving average.
/// </summary>
HMA,
/// <summary>
/// Smoothed moving average.
/// </summary>
SMMA,
}
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<decimal> _sellPercentage;
private readonly StrategyParam<MaMethods> _maMethod;
private readonly StrategyParam<bool> _allowInitialBuy;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevClose;
private decimal? _prevMa;
private bool _initialDone;
/// <summary>
/// Moving average length.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// Percent of the position sold on a cross below the average.
/// </summary>
public decimal SellPercentage
{
get => _sellPercentage.Value;
set => _sellPercentage.Value = value;
}
/// <summary>
/// Moving average method.
/// </summary>
public MaMethods MaMethod
{
get => _maMethod.Value;
set => _maMethod.Value = value;
}
/// <summary>
/// Buy the full position on the first candle.
/// </summary>
public bool AllowInitialBuy
{
get => _allowInitialBuy.Value;
set => _allowInitialBuy.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FlexibleMovingAverageStrategy()
{
_maLength = Param(nameof(MaLength), 200)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving average length", "Indicators");
_sellPercentage = Param(nameof(SellPercentage), 100m)
.SetRange(0m, 100m)
.SetDisplay("Sell %", "Percent of the position sold on a cross below the average", "Trading");
_maMethod = Param(nameof(MaMethod), MaMethods.SMA)
.SetDisplay("MA Method", "Moving average method", "Indicators");
_allowInitialBuy = Param(nameof(AllowInitialBuy), true)
.SetDisplay("Initial Buy", "Buy the full position on the first candle", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = null;
_prevMa = null;
_initialDone = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = null;
_prevMa = null;
_initialDone = false;
var ma = CreateMovingAverage(MaMethod, MaLength);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private static DecimalLengthIndicator CreateMovingAverage(MaMethods method, int length)
{
return method switch
{
MaMethods.EMA => new ExponentialMovingAverage { Length = length },
MaMethods.WMA => new WeightedMovingAverage { Length = length },
MaMethods.HMA => new HullMovingAverage { Length = length },
MaMethods.SMMA => new SmoothedMovingAverage { Length = length },
_ => new SimpleMovingAverage { Length = length },
};
}
private void ProcessCandle(ICandleMessage candle, decimal ma)
{
if (candle.State != CandleStates.Finished)
return;
var prevClose = _prevClose;
var prevMa = _prevMa;
_prevClose = candle.ClosePrice;
_prevMa = ma;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_initialDone)
{
_initialDone = true;
if (AllowInitialBuy && Position < Volume)
{
BuyMarket(Volume - Position);
return;
}
}
if (prevClose is not decimal lastClose || prevMa is not decimal lastMa)
return;
var close = candle.ClosePrice;
if (lastClose <= lastMa && close > ma)
{
if (Position < Volume)
BuyMarket(Volume - Position);
}
else if (lastClose >= lastMa && close < ma && Position > 0)
{
var volume = SellPercentage >= 100m ? Position : RoundVolume(Position * SellPercentage / 100m);
if (volume > 0)
SellMarket(volume);
}
}
private decimal RoundVolume(decimal volume)
{
var step = Security?.VolumeStep ?? 0m;
return step > 0 ? Math.Floor(volume / step) * step : volume;
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, ExponentialMovingAverage, WeightedMovingAverage, HullMovingAverage, SmoothedMovingAverage
from StockSharp.Algo.Strategies import Strategy
MA_SMA = 0
MA_EMA = 1
MA_WMA = 2
MA_HMA = 3
MA_SMMA = 4
class flexible_moving_average_strategy(Strategy):
"""
Flexible Moving Average strategy.
A long-only position is sized against a moving average of the chosen method. With AllowInitialBuy the full position is bought
on the first tradable candle. When a candle closes above the average after closing at or below it, the position is restored
to the full Volume; when it closes below after closing at or above, the position is reduced by SellPercentage percent.
"""
def __init__(self):
super(flexible_moving_average_strategy, self).__init__()
self._ma_length = self.Param("MaLength", 200).SetGreaterThanZero().SetDisplay("MA Length", "Moving average length", "Indicators")
self._sell_percentage = self.Param("SellPercentage", 100.0).SetDisplay("Sell %", "Percent of the position sold on a cross below the average", "Trading")
self._ma_method = self.Param("MaMethod", MA_SMA).SetDisplay("MA Method", "Moving average method (0 SMA, 1 EMA, 2 WMA, 3 HMA, 4 SMMA)", "Indicators")
self._allow_initial_buy = self.Param("AllowInitialBuy", True).SetDisplay("Initial Buy", "Buy the full position on the first candle", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_close = None
self._prev_ma = None
self._initial_done = False
def OnReseted(self):
super(flexible_moving_average_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(flexible_moving_average_strategy, self).OnStarted2(time)
self._reset_state()
ma = self._create_ma(int(self._ma_method.Value), self._ma_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _create_ma(self, method, length):
if method == MA_EMA:
ma = ExponentialMovingAverage()
elif method == MA_WMA:
ma = WeightedMovingAverage()
elif method == MA_HMA:
ma = HullMovingAverage()
elif method == MA_SMMA:
ma = SmoothedMovingAverage()
else:
ma = SimpleMovingAverage()
ma.Length = length
return ma
def _process_candle(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
ma = float(ma_value)
close = float(candle.ClosePrice)
last_close = self._prev_close
last_ma = self._prev_ma
self._prev_close = close
self._prev_ma = ma
if not self.IsFormedAndOnlineAndAllowTrading():
return
if not self._initial_done:
self._initial_done = True
if self._allow_initial_buy.Value and self.Position < self.Volume:
self.BuyMarket(self.Volume - self.Position)
return
if last_close is None or last_ma is None:
return
if last_close <= last_ma and close > ma:
if self.Position < self.Volume:
self.BuyMarket(self.Volume - self.Position)
elif last_close >= last_ma and close < ma and self.Position > 0:
percent = float(self._sell_percentage.Value)
if percent >= 100.0:
volume = float(self.Position)
else:
volume = self._round_volume(float(self.Position) * percent / 100.0)
if volume > 0:
self.SellMarket(Decimal(volume))
def _round_volume(self, volume):
step = float(self.Security.VolumeStep) if self.Security is not None and self.Security.VolumeStep is not None else 0.0
return math.floor(volume / step) * step if step > 0 else volume
def CreateClone(self):
return flexible_moving_average_strategy()