MA Volume 戦略
MA Volume は、移動平均のトレンドフィルターと出来高の急増を組み合わせてエントリーのタイミングを計ります。 価格が平均を上回りながら出来高が増加していることは強い買い集めを示し、平均を下回りながら出来高が減少することは分散を示します。
テストでは平均年間リターンは約136%を示しています。株式市場で最も良いパフォーマンスを発揮します。
この戦略は出来高が拡大したときに移動平均の方向でトレードし、出来高が枯渇するか平均が反転したときにイグジットします。
パーセントストップがトレンドの急激な変化から保護します。
詳細
- エントリー条件: インジケーターシグナル
- ロング/ショート: 両方
- エグジット条件: ストップロスまたは反対シグナル
- ストップ: はい、パーセントベース
- デフォルト値:
CandleType= 15 minuteStopLossPercent= 2MaPeriod= 20VolumePeriod= 20VolumeThreshold= 1.2 足の出来高が直前VolumePeriod本の平均のVolumeThreshold倍を超えると出来高拡大とみなします。ノーポジション時、拡大した足が上昇中のSMAより上で引ければ買い、下降中のSMAより下で引ければ売ります。出来高が平均を下回るか、SMAが逆方向に転じたら決済します。
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: 両方
- インジケーター: Moving Average, Volume
- ストップ: はい
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MA Volume strategy.
/// Volume expands when a candle's volume exceeds VolumeThreshold times the average of the previous VolumePeriod candles.
/// While flat, an expanding candle that closes above a rising MaPeriod SMA goes long and one that closes below a falling SMA goes short.
/// The position closes once volume dries up below its average or the SMA turns against it, and a percent stop limits the loss.
/// </summary>
public class MaVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<decimal> _volumeThreshold;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _volumes = [];
private decimal? _prevMa;
/// <summary>
/// Period of the trend SMA.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Previous candles the volume is averaged over.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// How many times the average volume an expanding candle must exceed.
/// </summary>
public decimal VolumeThreshold
{
get => _volumeThreshold.Value;
set => _volumeThreshold.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MaVolumeStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period of the trend SMA", "Indicators");
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Indicators");
_volumeThreshold = Param(nameof(VolumeThreshold), 1.2m)
.SetGreaterThanZero()
.SetDisplay("Volume Threshold", "How many times the average volume an expanding candle must exceed", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_volumes.Clear();
_prevMa = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumes.Clear();
_prevMa = null;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
// Expansion is measured against the candles before this one.
var average = _volumes.Count == VolumePeriod ? _volumes.Average() : (decimal?)null;
_volumes.Add(candle.TotalVolume);
if (_volumes.Count > VolumePeriod)
_volumes.RemoveAt(0);
if (!smaValue.IsFormed)
return;
var ma = smaValue.GetValue<decimal>();
var prevMa = _prevMa;
_prevMa = ma;
if (average is not decimal avgVolume || prevMa is not decimal lastMa)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var volume = candle.TotalVolume;
if (Position > 0)
{
if (volume < avgVolume || ma < lastMa)
SellMarket(Position);
}
else if (Position < 0)
{
if (volume < avgVolume || ma > lastMa)
BuyMarket(-Position);
}
else if (volume > avgVolume * VolumeThreshold)
{
if (close > ma && ma > lastMa)
BuyMarket(Volume);
else if (close < ma && ma < lastMa)
SellMarket(Volume);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_volume_strategy(Strategy):
"""
MA Volume strategy.
Volume expands when a candle's volume exceeds VolumeThreshold times the average of the previous VolumePeriod candles.
While flat, an expanding candle that closes above a rising MaPeriod SMA goes long and one that closes below a falling SMA goes short.
The position closes once volume dries up below its average or the SMA turns against it, and a percent stop limits the loss.
"""
def __init__(self):
super(ma_volume_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Period of the trend SMA", "Indicators")
self._volume_period = self.Param("VolumePeriod", 20).SetGreaterThanZero().SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Indicators")
self._volume_threshold = self.Param("VolumeThreshold", 1.2).SetGreaterThanZero().SetDisplay("Volume Threshold", "How many times the average volume an expanding candle must exceed", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).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._volumes = []
self._prev_ma = None
def OnReseted(self):
super(ma_volume_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ma_volume_strategy, self).OnStarted2(time)
self._reset_state()
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
# Expansion is measured against the candles before this one.
period = self._volume_period.Value
average = None
if len(self._volumes) == period:
total = Decimal(0)
for volume in self._volumes:
total += volume
average = total / Decimal(period)
self._volumes.append(candle.TotalVolume)
if len(self._volumes) > period:
self._volumes.pop(0)
if not sma_value.IsFormed:
return
ma = sma_value.GetValue[Decimal](None)
prev_ma = self._prev_ma
self._prev_ma = ma
if average is None or prev_ma is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
volume = candle.TotalVolume
if self.Position > 0:
if volume < average or ma < prev_ma:
self.SellMarket(self.Position)
elif self.Position < 0:
if volume < average or ma > prev_ma:
self.BuyMarket(-self.Position)
elif volume > average * Decimal(self._volume_threshold.Value):
if close > ma and ma > prev_ma:
self.BuyMarket(self.Volume)
elif close < ma and ma < prev_ma:
self.SellMarket(self.Volume)
def CreateClone(self):
return ma_volume_strategy()