出来高MAクロス (Volume MA Cross)
この戦略は出来高を高速・低速の移動平均で処理します。高速出来高MAが低速出来高MAを上抜けると、参加者の増加を示してロングエントリーが発動します。下抜けは弱さのシグナルとなりショートを開始します。
テストでは年平均リターンが約46%となっています。株式市場での運用に最も適しています。
逆クロスが発生したときにポジションを決済します。価格は独自の移動平均で監視され、取引のフィルタリングに役立てられます。
出来高ベースのシグナルは価格動向に先行することが多く、早期エントリーが可能です。
詳細
- エントリー条件: 高速出来高MAが低速出来高MAをクロス。
- ロング/ショート: 両方。
- エグジット条件: 逆クロスまたはストップ。
- ストップ: はい。
- デフォルト値:
FastVolumeMALength= 10SlowVolumeMALength= 50CandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: モメンタム
- 方向: 両方
- インジケーター: Volume MA
- ストップ: はい
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Trades actual fast/slow native TotalVolume SMA crosses with Close/SMA entry confirmation.
/// Fully exits on the opposite volume cross or actual-fill percent protection.
/// </summary>
public class VolumeMAXrossStrategy : Strategy
{
private readonly StrategyParam<int> _priceMaPeriod;
private readonly StrategyParam<int> _fastVolumeMaLength;
private readonly StrategyParam<int> _slowVolumeMaLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private SimpleMovingAverage _priceMa;
private SimpleMovingAverage _fastVolumeMa;
private SimpleMovingAverage _slowVolumeMa;
private decimal? _previousFast;
private decimal? _previousSlow;
private Order _pendingOrder;
public int PriceMaPeriod { get => _priceMaPeriod.Value; set => _priceMaPeriod.Value = value; }
public int FastVolumeMALength { get => _fastVolumeMaLength.Value; set => _fastVolumeMaLength.Value = value; }
public int SlowVolumeMALength { get => _slowVolumeMaLength.Value; set => _slowVolumeMaLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public VolumeMAXrossStrategy()
{
_priceMaPeriod = Param(nameof(PriceMaPeriod), 20).SetGreaterThanZero()
.SetDisplay("Price MA Period", "Current-inclusive Close SMA entry filter", "Indicators");
_fastVolumeMaLength = Param(nameof(FastVolumeMALength), 10).SetGreaterThanZero()
.SetDisplay("Fast Volume MA Length", "Current-inclusive fast TotalVolume SMA length", "Indicators");
_slowVolumeMaLength = Param(nameof(SlowVolumeMALength), 50).SetGreaterThanZero()
.SetDisplay("Slow Volume MA Length", "Current-inclusive slow TotalVolume SMA length", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
OrderRegistering += order => _pendingOrder = order;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearSignalState();
}
private void ClearSignalState()
{
_priceMa = null;
_fastVolumeMa = null;
_slowVolumeMa = null;
_previousFast = null;
_previousSlow = null;
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearSignalState();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
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();
}
_priceMa = new SimpleMovingAverage { Length = PriceMaPeriod, Name = "Price SMA" };
_fastVolumeMa = new SimpleMovingAverage { Length = FastVolumeMALength, Name = "Fast volume SMA" };
_slowVolumeMa = new SimpleMovingAverage { Length = SlowVolumeMALength, Name = "Slow volume SMA" };
Indicators.Add(_priceMa);
Indicators.Add(_fastVolumeMa);
Indicators.Add(_slowVolumeMa);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _priceMa);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before this callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var priceValue = _priceMa.Process(candle);
// Process actual candle TotalVolume, not a price or previous-volume proxy.
var fastValue = _fastVolumeMa.Process(new DecimalIndicatorValue(_fastVolumeMa, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
var slowValue = _slowVolumeMa.Process(new DecimalIndicatorValue(_slowVolumeMa, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
decimal? priceMa = !priceValue.IsEmpty && _priceMa.IsFormed ? priceValue.GetValue<decimal>() : null;
decimal? fast = !fastValue.IsEmpty && _fastVolumeMa.IsFormed ? fastValue.GetValue<decimal>() : null;
decimal? slow = !slowValue.IsEmpty && _slowVolumeMa.IsFormed ? slowValue.GetValue<decimal>() : null;
var up = _previousFast is decimal oldFastUp && _previousSlow is decimal oldSlowUp &&
fast is decimal currentFastUp && slow is decimal currentSlowUp &&
oldFastUp <= oldSlowUp && currentFastUp > currentSlowUp;
var down = _previousFast is decimal oldFastDown && _previousSlow is decimal oldSlowDown &&
fast is decimal currentFastDown && slow is decimal currentSlowDown &&
oldFastDown >= oldSlowDown && currentFastDown < currentSlowDown;
// Independently formed values seed the next cross even while trading is unavailable.
_previousFast = fast;
_previousSlow = slow;
if (!IsFormedAndOnlineAndAllowTrading() ||
_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
if (Position > 0m && down) SellMarket(Position);
else if (Position < 0m && up) BuyMarket(Math.Abs(Position));
else if (Position == 0m && priceMa is decimal mean)
{
if (up && candle.ClosePrice > mean) BuyMarket(Volume);
else if (down && candle.ClosePrice < mean) 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, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage, DecimalIndicatorValue, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class volume_ma_cross_strategy(Strategy):
"""
Trades actual fast/slow native TotalVolume SMA crosses with Close/SMA entry confirmation.
Fully exits on the opposite volume cross or actual-fill percent protection.
"""
def __init__(self):
super(volume_ma_cross_strategy, self).__init__()
self._price_ma_period = self.Param("PriceMaPeriod", 20).SetGreaterThanZero().SetDisplay("Price MA Period", "Current-inclusive Close SMA entry filter", "Indicators")
self._fast_volume_ma_length = self.Param("FastVolumeMALength", 10).SetGreaterThanZero().SetDisplay("Fast Volume MA Length", "Current-inclusive fast TotalVolume SMA length", "Indicators")
self._slow_volume_ma_length = self.Param("SlowVolumeMALength", 50).SetGreaterThanZero().SetDisplay("Slow Volume MA Length", "Current-inclusive slow TotalVolume SMA length", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._price_ma = None
self._fast_volume_ma = None
self._slow_volume_ma = None
self._previous_fast = None
self._previous_slow = None
self._pending_order = None
self.OrderRegistering += self._track_pending
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
def _clear_signal_state(self):
self._price_ma = None
self._fast_volume_ma = None
self._slow_volume_ma = None
self._previous_fast = None
self._previous_slow = None
self._pending_order = None
def OnReseted(self):
super(volume_ma_cross_strategy, self).OnReseted()
self._clear_signal_state()
def OnStarted2(self, time):
super(volume_ma_cross_strategy, self).OnStarted2(time)
self._clear_signal_state()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
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()
self._price_ma = SimpleMovingAverage()
self._price_ma.Length = self._price_ma_period.Value
self._price_ma.Name = "Price SMA"
self._fast_volume_ma = SimpleMovingAverage()
self._fast_volume_ma.Length = self._fast_volume_ma_length.Value
self._fast_volume_ma.Name = "Fast volume SMA"
self._slow_volume_ma = SimpleMovingAverage()
self._slow_volume_ma.Length = self._slow_volume_ma_length.Value
self._slow_volume_ma.Name = "Slow volume SMA"
self.Indicators.Add(self._price_ma)
self.Indicators.Add(self._fast_volume_ma)
self.Indicators.Add(self._slow_volume_ma)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._price_ma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between finished candles.
pass
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price_value = self._price_ma.Process(CandleIndicatorValue(self._price_ma, candle))
# Use actual current TotalVolume for both native SMAs, including while unavailable.
fast_input = DecimalIndicatorValue(self._fast_volume_ma, candle.TotalVolume, candle.OpenTime)
fast_input.IsFinal = True
fast_value = self._fast_volume_ma.Process(fast_input)
slow_input = DecimalIndicatorValue(self._slow_volume_ma, candle.TotalVolume, candle.OpenTime)
slow_input.IsFinal = True
slow_value = self._slow_volume_ma.Process(slow_input)
price_ma = price_value.GetValue[Decimal](None) if not price_value.IsEmpty and self._price_ma.IsFormed else None
fast = fast_value.GetValue[Decimal](None) if not fast_value.IsEmpty and self._fast_volume_ma.IsFormed else None
slow = slow_value.GetValue[Decimal](None) if not slow_value.IsEmpty and self._slow_volume_ma.IsFormed else None
up = self._previous_fast is not None and self._previous_slow is not None and fast is not None and slow is not None and self._previous_fast <= self._previous_slow and fast > slow
down = self._previous_fast is not None and self._previous_slow is not None and fast is not None and slow is not None and self._previous_fast >= self._previous_slow and fast < slow
# Formed values seed the next cross even while trading is disabled or pending.
self._previous_fast = fast
self._previous_slow = slow
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
if self.Position > 0 and down:
self.SellMarket(self.Position)
elif self.Position < 0 and up:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and price_ma is not None:
if up and candle.ClosePrice > price_ma:
self.BuyMarket(self.Volume)
elif down and candle.ClosePrice < price_ma:
self.SellMarket(self.Volume)
def CreateClone(self):
return volume_ma_cross_strategy()