フレキシブル移動平均戦略
前の期間の終値と設定可能な移動平均のクロスオーバーに基づいてポジションを調整します。下向きのクロスではユーザー定義の割合でポジションを縮小し、上向きのクロスではフルポジションに戻します。
詳細
- エントリー条件:
- 初回: 最初のバーでオプションのフルロング。
- 増加: 前の終値が移動平均を上抜け → ポジションを100%へ。
- エグジット条件:
- 縮小: 前の終値が移動平均を下抜け →
SellPercentageだけ縮小。
- 縮小: 前の終値が移動平均を下抜け →
- インジケーター:
- 単純、指数、加重、Hull、または平滑移動平均。
- ストップ: なし。
- デフォルト値:
MaLength= 200SellPercentage= 100MaMethod= SMAAllowInitialBuy= true
- フィルター:
- トレンドフォロー
- 単一時間軸
- インジケーター: 移動平均
- ストップ: なし
- 複雑さ: 基本
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()