NAS100 和黄金 Smart Scalping Strategy PRO Enhanced v2
该策略利用 EMA9 和 VWAP 作为动态参考,RSI 衡量动量,ATR 控制风险。15 分钟 EMA200 趋势过滤器让交易顺势而为,并结合成交量放大过滤强势K线。策略按风险计算仓位,可选跟踪止损并在交易之间设置冷却时间。
细节
- 入场条件: 指标信号
- 多空方向: 双向
- 出场条件: 止损、止盈或反向信号
- 止损: 是,基于 ATR
- 默认值:
CandleType= 1 分钟RiskPercent= 1%AtrMultiplierSl= 1AtrMultiplierTp= 2CooldownMins= 30StartHour= 13EndHour= 20
- 过滤器:
- 类别: 高频/剥头皮
- 方向: 双向
- 指标: EMA, VWAP, RSI, ATR, EMA200
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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>
/// NAS100 and gold smart scalping strategy.
/// Between StartHour and EndHour (UTC) a long opens when the close is above EMA9, the session VWAP and the 15 minute EMA200,
/// RSI is above 50 and volume spikes above its average; a short is the mirror image. An opposite signal reverses the position.
/// The stop loss and take profit are ATR multiples from the entry, optionally trailing, the size risks RiskPercent of equity
/// on the stop distance and a new entry waits CooldownMins after the previous one.
/// </summary>
public class Nas100AndGoldSmartScalpingProEnhancedV2Strategy : Strategy
{
private const int _emaLength = 9;
private const int _rsiLength = 14;
private const int _atrLength = 14;
private const int _trendLength = 200;
private const int _volumeLength = 20;
private const decimal _volumeSpikeFactor = 1.5m;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<decimal> _atrMultiplierSl;
private readonly StrategyParam<decimal> _atrMultiplierTp;
private readonly StrategyParam<int> _cooldownMins;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<bool> _useTrailing;
private decimal? _trendEma;
private DateTime _vwapDate;
private decimal _vwapPriceVolume;
private decimal _vwapVolume;
private DateTime? _lastEntryTime;
private decimal _stopPrice;
private decimal _takePrice;
private decimal _stopDistance;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Percent of equity risked on the stop distance.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// ATR multiplier for the stop loss.
/// </summary>
public decimal AtrMultiplierSl
{
get => _atrMultiplierSl.Value;
set => _atrMultiplierSl.Value = value;
}
/// <summary>
/// ATR multiplier for the take profit.
/// </summary>
public decimal AtrMultiplierTp
{
get => _atrMultiplierTp.Value;
set => _atrMultiplierTp.Value = value;
}
/// <summary>
/// Minutes to wait after an entry before the next one.
/// </summary>
public int CooldownMins
{
get => _cooldownMins.Value;
set => _cooldownMins.Value = value;
}
/// <summary>
/// Session start hour (UTC).
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Session end hour (UTC).
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// Trail the stop by the ATR stop distance.
/// </summary>
public bool UseTrailing
{
get => _useTrailing.Value;
set => _useTrailing.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public Nas100AndGoldSmartScalpingProEnhancedV2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_riskPercent = Param(nameof(RiskPercent), 1m)
.SetGreaterThanZero()
.SetDisplay("Risk %", "Percent of equity risked per trade", "Risk");
_atrMultiplierSl = Param(nameof(AtrMultiplierSl), 1m)
.SetGreaterThanZero()
.SetDisplay("ATR SL Mult", "ATR multiplier for the stop loss", "Risk");
_atrMultiplierTp = Param(nameof(AtrMultiplierTp), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR TP Mult", "ATR multiplier for the take profit", "Risk");
_cooldownMins = Param(nameof(CooldownMins), 30)
.SetNotNegative()
.SetDisplay("Cooldown (min)", "Minutes between entries", "General");
_startHour = Param(nameof(StartHour), 13)
.SetRange(0, 23)
.SetDisplay("Start Hour", "Session start hour (UTC)", "Session");
_endHour = Param(nameof(EndHour), 20)
.SetRange(0, 24)
.SetDisplay("End Hour", "Session end hour (UTC)", "Session");
_useTrailing = Param(nameof(UseTrailing), false)
.SetDisplay("Use Trailing", "Trail the stop by the ATR stop distance", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, TimeSpan.FromMinutes(15).TimeFrame())];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_trendEma = null;
_vwapDate = default;
_vwapPriceVolume = 0m;
_vwapVolume = 0m;
_lastEntryTime = null;
_stopPrice = 0m;
_takePrice = 0m;
_stopDistance = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var ema = new ExponentialMovingAverage { Length = _emaLength };
var rsi = new RelativeStrengthIndex { Length = _rsiLength };
var atr = new AverageTrueRange { Length = _atrLength };
var volumeAverage = new SimpleMovingAverage { Length = _volumeLength };
var trendEma = new ExponentialMovingAverage { Length = _trendLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, rsi, atr, (candle, emaValue, rsiValue, atrValue) => ProcessCandle(candle, emaValue, rsiValue, atrValue, volumeAverage))
.Start();
SubscribeCandles(TimeSpan.FromMinutes(15).TimeFrame())
.Bind(trendEma, (candle, value) =>
{
if (candle.State == CandleStates.Finished && trendEma.IsFormed)
_trendEma = value;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rsiValue, decimal atrValue, SimpleMovingAverage volumeAverage)
{
if (candle.State != CandleStates.Finished)
return;
var avgValue = volumeAverage.Process(new DecimalIndicatorValue(volumeAverage, candle.TotalVolume, candle.ServerTime) { IsFinal = true });
// Session VWAP restarts every UTC day.
var date = candle.OpenTime.Date;
if (date != _vwapDate)
{
_vwapDate = date;
_vwapPriceVolume = 0m;
_vwapVolume = 0m;
}
var typical = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;
_vwapPriceVolume += typical * candle.TotalVolume;
_vwapVolume += candle.TotalVolume;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (ManagePosition(candle))
return;
if (_trendEma is not decimal trend || !volumeAverage.IsFormed || _vwapVolume <= 0 || atrValue <= 0)
return;
var hour = candle.OpenTime.Hour;
if (hour < StartHour || hour >= EndHour)
return;
if (_lastEntryTime is DateTime last && candle.OpenTime - last < TimeSpan.FromMinutes(CooldownMins))
return;
var close = candle.ClosePrice;
var vwap = _vwapPriceVolume / _vwapVolume;
var volumeSpike = candle.TotalVolume > avgValue.ToDecimal() * _volumeSpikeFactor;
var longSignal = close > emaValue && close > vwap && rsiValue > 50 && close > trend && volumeSpike;
var shortSignal = close < emaValue && close < vwap && rsiValue < 50 && close < trend && volumeSpike;
if (longSignal && Position <= 0)
{
Enter(candle, true, atrValue);
}
else if (shortSignal && Position >= 0)
{
Enter(candle, false, atrValue);
}
}
private void Enter(ICandleMessage candle, bool isLong, decimal atr)
{
var close = candle.ClosePrice;
_stopDistance = atr * AtrMultiplierSl;
var volume = Volume;
var equity = Portfolio?.CurrentValue ?? 0m;
if (equity > 0 && _stopDistance > 0)
{
var riskVolume = equity * RiskPercent / 100m / _stopDistance;
if (riskVolume > 0)
volume = riskVolume;
}
if (isLong)
{
BuyMarket(volume + Math.Abs(Position));
_stopPrice = close - _stopDistance;
_takePrice = close + atr * AtrMultiplierTp;
}
else
{
SellMarket(volume + Math.Abs(Position));
_stopPrice = close + _stopDistance;
_takePrice = close - atr * AtrMultiplierTp;
}
_lastEntryTime = candle.OpenTime;
}
private bool ManagePosition(ICandleMessage candle)
{
if (Position > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
{
SellMarket(Position);
return true;
}
if (UseTrailing)
_stopPrice = Math.Max(_stopPrice, candle.ClosePrice - _stopDistance);
}
else if (Position < 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)
{
BuyMarket(-Position);
return true;
}
if (UseTrailing)
_stopPrice = Math.Min(_stopPrice, candle.ClosePrice + _stopDistance);
}
return false;
}
}
import clr
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 ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
EMA_LENGTH = 9
RSI_LENGTH = 14
ATR_LENGTH = 14
TREND_LENGTH = 200
VOLUME_LENGTH = 20
VOLUME_SPIKE_FACTOR = 1.5
class nas100_and_gold_smart_scalping_pro_enhanced_v2_strategy(Strategy):
"""
NAS100 and gold smart scalping strategy.
Between StartHour and EndHour (UTC) a long opens when the close is above EMA9, the session VWAP and the 15 minute EMA200,
RSI is above 50 and volume spikes above its average; a short is the mirror image. An opposite signal reverses the position.
The stop loss and take profit are ATR multiples from the entry, optionally trailing, the size risks RiskPercent of equity
on the stop distance and a new entry waits CooldownMins after the previous one.
"""
def __init__(self):
super(nas100_and_gold_smart_scalping_pro_enhanced_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._risk_percent = self.Param("RiskPercent", 1.0).SetGreaterThanZero().SetDisplay("Risk %", "Percent of equity risked per trade", "Risk")
self._atr_multiplier_sl = self.Param("AtrMultiplierSl", 1.0).SetGreaterThanZero().SetDisplay("ATR SL Mult", "ATR multiplier for the stop loss", "Risk")
self._atr_multiplier_tp = self.Param("AtrMultiplierTp", 2.0).SetGreaterThanZero().SetDisplay("ATR TP Mult", "ATR multiplier for the take profit", "Risk")
self._cooldown_mins = self.Param("CooldownMins", 30).SetNotNegative().SetDisplay("Cooldown (min)", "Minutes between entries", "General")
self._start_hour = self.Param("StartHour", 13).SetRange(0, 23).SetDisplay("Start Hour", "Session start hour (UTC)", "Session")
self._end_hour = self.Param("EndHour", 20).SetRange(0, 24).SetDisplay("End Hour", "Session end hour (UTC)", "Session")
self._use_trailing = self.Param("UseTrailing", False).SetDisplay("Use Trailing", "Trail the stop by the ATR stop distance", "Risk")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.TimeFrame(TimeSpan.FromMinutes(15)))]
def _reset_state(self):
self._trend_ema = None
self._volumes = []
self._vwap_date = None
self._vwap_price_volume = 0.0
self._vwap_volume = 0.0
self._last_entry_time = None
self._stop_price = 0.0
self._take_price = 0.0
self._stop_distance = 0.0
def OnReseted(self):
super(nas100_and_gold_smart_scalping_pro_enhanced_v2_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(nas100_and_gold_smart_scalping_pro_enhanced_v2_strategy, self).OnStarted2(time)
self._reset_state()
ema = ExponentialMovingAverage()
ema.Length = EMA_LENGTH
rsi = RelativeStrengthIndex()
rsi.Length = RSI_LENGTH
atr = AverageTrueRange()
atr.Length = ATR_LENGTH
self._trend_indicator = ExponentialMovingAverage()
self._trend_indicator.Length = TREND_LENGTH
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, rsi, atr, self._process_candle).Start()
self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(15))).Bind(self._trend_indicator, self._process_trend).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_trend(self, candle, value):
if candle.State == CandleStates.Finished and self._trend_indicator.IsFormed:
self._trend_ema = float(value)
def _process_candle(self, candle, ema_value, rsi_value, atr_value):
if candle.State != CandleStates.Finished:
return
volume = float(candle.TotalVolume)
self._volumes.append(volume)
if len(self._volumes) > VOLUME_LENGTH:
self._volumes.pop(0)
# Session VWAP restarts every UTC day.
open_time = candle.OpenTime
date = open_time.Date
if self._vwap_date is None or date != self._vwap_date:
self._vwap_date = date
self._vwap_price_volume = 0.0
self._vwap_volume = 0.0
typical = (float(candle.HighPrice) + float(candle.LowPrice) + float(candle.ClosePrice)) / 3.0
self._vwap_price_volume += typical * volume
self._vwap_volume += volume
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._manage_position(candle):
return
atr = float(atr_value)
if self._trend_ema is None or len(self._volumes) < VOLUME_LENGTH or self._vwap_volume <= 0 or atr <= 0:
return
hour = open_time.Hour
if hour < self._start_hour.Value or hour >= self._end_hour.Value:
return
if self._last_entry_time is not None and (open_time - self._last_entry_time).TotalMinutes < self._cooldown_mins.Value:
return
close = float(candle.ClosePrice)
ema = float(ema_value)
rsi = float(rsi_value)
vwap = self._vwap_price_volume / self._vwap_volume
volume_spike = volume > sum(self._volumes) / len(self._volumes) * VOLUME_SPIKE_FACTOR
long_signal = close > ema and close > vwap and rsi > 50 and close > self._trend_ema and volume_spike
short_signal = close < ema and close < vwap and rsi < 50 and close < self._trend_ema and volume_spike
if long_signal and self.Position <= 0:
self._enter(candle, True, atr)
elif short_signal and self.Position >= 0:
self._enter(candle, False, atr)
def _enter(self, candle, is_long, atr):
close = float(candle.ClosePrice)
self._stop_distance = atr * float(self._atr_multiplier_sl.Value)
volume = self.Volume
portfolio = self.Portfolio
equity = float(portfolio.CurrentValue) if portfolio is not None and portfolio.CurrentValue is not None else 0.0
if equity > 0 and self._stop_distance > 0:
risk_volume = equity * float(self._risk_percent.Value) / 100.0 / self._stop_distance
if risk_volume > 0:
volume = Decimal(risk_volume)
take_distance = atr * float(self._atr_multiplier_tp.Value)
if is_long:
self.BuyMarket(volume + abs(self.Position))
self._stop_price = close - self._stop_distance
self._take_price = close + take_distance
else:
self.SellMarket(volume + abs(self.Position))
self._stop_price = close + self._stop_distance
self._take_price = close - take_distance
self._last_entry_time = candle.OpenTime
def _manage_position(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self.Position > 0:
if low <= self._stop_price or high >= self._take_price:
self.SellMarket(self.Position)
return True
if self._use_trailing.Value:
self._stop_price = max(self._stop_price, close - self._stop_distance)
elif self.Position < 0:
if high >= self._stop_price or low <= self._take_price:
self.BuyMarket(-self.Position)
return True
if self._use_trailing.Value:
self._stop_price = min(self._stop_price, close + self._stop_distance)
return False
def CreateClone(self):
return nas100_and_gold_smart_scalping_pro_enhanced_v2_strategy()