Volume EA 策略
概述
该策略结合成交量激增与商品通道指数 (CCI) 进行交易。当上一根蜡烛的成交量超过更早一根蜡烛的成交量乘以 Factor 时,在新一小时的开始开仓。CCI 必须处于指定区间内以确认信号。
规则
- 同时只持有一个头寸。
- 每个小时的开始:
- 做多 条件:
- 上一根蜡烛收阳。
- 上一成交量 > 前一成交量 ×
Factor。 - CCI 介于
CciLevel1与CciLevel2之间。
- 做空 条件:
- 上一根蜡烛收阴。
- 上一成交量 > 前一成交量 ×
Factor。 - CCI 介于
CciLevel4与CciLevel3之间。
- 做多 条件:
- 使用
TrailingStop价格步长的移动止损保护利润。 - 当小时数等于 23 时平掉所有仓位。
参数
Factor– 成交量倍数阈值。TrailingStop– 移动止损距离(价格步长)。CciLevel1/CciLevel2– 多头交易的 CCI 区间。CciLevel3/CciLevel4– 空头交易的 CCI 区间。CandleType– 用于计算的蜡烛周期。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on volume spikes and CCI ranges.
/// </summary>
public class VolumeEaStrategy : Strategy
{
private readonly StrategyParam<decimal> _factor;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<decimal> _cciLevel1;
private readonly StrategyParam<decimal> _cciLevel2;
private readonly StrategyParam<decimal> _cciLevel3;
private readonly StrategyParam<decimal> _cciLevel4;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevVolume;
private decimal _prevPrevVolume;
private decimal _prevOpen;
private decimal _prevClose;
private decimal _longStop;
private decimal _shortStop;
/// <summary>
/// Volume multiplier threshold.
/// </summary>
public decimal Factor
{
get => _factor.Value;
set => _factor.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public decimal TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Lower CCI level for long trades.
/// </summary>
public decimal CciLevel1
{
get => _cciLevel1.Value;
set => _cciLevel1.Value = value;
}
/// <summary>
/// Upper CCI level for long trades.
/// </summary>
public decimal CciLevel2
{
get => _cciLevel2.Value;
set => _cciLevel2.Value = value;
}
/// <summary>
/// Upper CCI level for short trades.
/// </summary>
public decimal CciLevel3
{
get => _cciLevel3.Value;
set => _cciLevel3.Value = value;
}
/// <summary>
/// Lower CCI level for short trades.
/// </summary>
public decimal CciLevel4
{
get => _cciLevel4.Value;
set => _cciLevel4.Value = value;
}
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public VolumeEaStrategy()
{
_factor = Param(nameof(Factor), 1.55m)
.SetGreaterThanZero()
.SetDisplay("Factor", "Volume multiplier", "Trading");
_trailingStop = Param(nameof(TrailingStop), 350m)
.SetGreaterThanZero()
.SetDisplay("Trailing Stop", "Trailing distance in steps", "Risk");
_cciLevel1 = Param(nameof(CciLevel1), 50m)
.SetDisplay("CCI Level1", "Lower CCI for buys", "Trading");
_cciLevel2 = Param(nameof(CciLevel2), 190m)
.SetDisplay("CCI Level2", "Upper CCI for buys", "Trading");
_cciLevel3 = Param(nameof(CciLevel3), -50m)
.SetDisplay("CCI Level3", "Upper CCI for sells", "Trading");
_cciLevel4 = Param(nameof(CciLevel4), -190m)
.SetDisplay("CCI Level4", "Lower CCI for sells", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevVolume = 0;
_prevPrevVolume = 0;
_prevOpen = 0;
_prevClose = 0;
_longStop = 0;
_shortStop = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var cci = new CommodityChannelIndex { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, cci);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
var currentVolume = candle.TotalVolume;
{
var trailDist = TrailingStop;
var volumeOk = _prevVolume > _prevPrevVolume * Factor;
if (volumeOk)
{
if (_prevClose > _prevOpen && cciValue > CciLevel1 && cciValue < CciLevel2 && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_longStop = candle.ClosePrice - trailDist;
_shortStop = 0m;
}
else if (_prevClose < _prevOpen && cciValue < CciLevel3 && cciValue > CciLevel4 && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_shortStop = candle.ClosePrice + trailDist;
_longStop = 0m;
}
}
if (Position > 0)
{
var candidate = candle.ClosePrice - trailDist;
if (candidate > _longStop)
_longStop = candidate;
if (candle.ClosePrice <= _longStop)
{
SellMarket();
_longStop = 0m;
}
}
else if (Position < 0)
{
var candidate = candle.ClosePrice + trailDist;
if (_shortStop == 0m || candidate < _shortStop)
_shortStop = candidate;
if (candle.ClosePrice >= _shortStop)
{
BuyMarket();
_shortStop = 0m;
}
}
}
_prevPrevVolume = _prevVolume;
_prevVolume = currentVolume;
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
}
}
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 CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class volume_ea_strategy(Strategy):
def __init__(self):
super(volume_ea_strategy, self).__init__()
self._factor = self.Param("Factor", 1.55) \
.SetDisplay("Factor", "Volume multiplier", "Trading")
self._trailing_stop = self.Param("TrailingStop", 350.0) \
.SetDisplay("Trailing Stop", "Trailing distance in steps", "Risk")
self._cci_level1 = self.Param("CciLevel1", 50) \
.SetDisplay("CCI Level1", "Lower CCI for buys", "Trading")
self._cci_level2 = self.Param("CciLevel2", 190) \
.SetDisplay("CCI Level2", "Upper CCI for buys", "Trading")
self._cci_level3 = self.Param("CciLevel3", -50) \
.SetDisplay("CCI Level3", "Upper CCI for sells", "Trading")
self._cci_level4 = self.Param("CciLevel4", -190.0) \
.SetDisplay("CCI Level4", "Lower CCI for sells", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._prev_volume = 0.0
self._prev_prev_volume = 0.0
self._prev_open = 0.0
self._prev_close = 0.0
self._long_stop = 0.0
self._short_stop = 0.0
@property
def factor(self):
return self._factor.Value
@property
def trailing_stop(self):
return self._trailing_stop.Value
@property
def cci_level1(self):
return self._cci_level1.Value
@property
def cci_level2(self):
return self._cci_level2.Value
@property
def cci_level3(self):
return self._cci_level3.Value
@property
def cci_level4(self):
return self._cci_level4.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(volume_ea_strategy, self).OnReseted()
self._prev_volume = 0.0
self._prev_prev_volume = 0.0
self._prev_open = 0.0
self._prev_close = 0.0
self._long_stop = 0.0
self._short_stop = 0.0
def OnStarted2(self, time):
super(volume_ea_strategy, self).OnStarted2(time)
cci = CommodityChannelIndex()
cci.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(cci, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, cci)
self.DrawOwnTrades(area)
def on_process(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
current_volume = candle.TotalVolume
trail_dist = self.trailing_stop
volume_ok = self._prev_volume > self._prev_prev_volume * self.factor
if volume_ok:
if self._prev_close > self._prev_open and cci_value > self.cci_level1 and cci_value < self.cci_level2 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._long_stop = candle.ClosePrice - trail_dist
self._short_stop = 0
elif self._prev_close < self._prev_open and cci_value < self.cci_level3 and cci_value > self.cci_level4 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._short_stop = candle.ClosePrice + trail_dist
self._long_stop = 0
if self.Position > 0:
candidate = candle.ClosePrice - trail_dist
if candidate > self._long_stop:
self._long_stop = candidate
if candle.ClosePrice <= self._long_stop:
self.SellMarket()
self._long_stop = 0
elif self.Position < 0:
candidate = candle.ClosePrice + trail_dist
if self._short_stop == 0 or candidate < self._short_stop:
self._short_stop = candidate
if candle.ClosePrice >= self._short_stop:
self.BuyMarket()
self._short_stop = 0
self._prev_prev_volume = self._prev_volume
self._prev_volume = current_volume
self._prev_open = candle.OpenPrice
self._prev_close = candle.ClosePrice
def CreateClone(self):
return volume_ea_strategy()