FVG Positioning Average with 200EMA Auto Trading 策略
该策略对多头和空头公平价值缺口(FVG)的水平进行平均,并结合200周期EMA。当价格沿趋势方向突破这些平均值时开仓。
详情
- 入场条件:
- 做多:价格向上突破空头FVG平均值,且所有平均值高于EMA。
- 做空:价格向下突破多头FVG平均值,且所有平均值低于EMA。
- 多空方向:双向。
- 出场条件:
- 在最近的低点/高点设置止损。
- 按风险回报比设置止盈。
- 止损:是。
- 默认值:
FvgLookback= 30AtrMultiplier= 0.25LookbackPeriod= 20EmaPeriod= 200RiskReward= 1.5
- 过滤器:
- 类别:价格行为
- 方向:双向
- 指标:ATR、EMA、SMA、Highest、Lowest
- 止损:是
- 复杂度:中等
- 时间框架:任意
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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>
/// FVG positioning average with 200 EMA strategy.
/// A bullish fair value gap is a candle whose low is above the high two candles back by more than AtrMultiplier ATRs, a bearish gap
/// the mirror. The levels of the last FvgLookback bullish and bearish gaps are averaged. A close crossing above the bearish average
/// while both averages are above the EMA goes long, a close crossing below the bullish average while both are below the EMA goes short.
/// The stop sits at the lowest low (highest high) of LookbackPeriod candles and the target at RiskReward times that risk.
/// </summary>
public class FvgPositioningAverageWith200EmaAutoTradingStrategy : Strategy
{
private const int _atrLength = 200;
private readonly StrategyParam<int> _fvgLookback;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _riskReward;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _bullLevels = [];
private readonly List<decimal> _bearLevels = [];
private decimal? _high1, _high2, _low1, _low2;
private decimal? _prevClose, _prevBullAvg, _prevBearAvg;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Number of recent gaps of each direction that are averaged.
/// </summary>
public int FvgLookback
{
get => _fvgLookback.Value;
set => _fvgLookback.Value = value;
}
/// <summary>
/// Minimum gap size in ATRs.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candles used for the stop at the recent low or high.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Trend EMA period.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Target distance as a multiple of the stop distance.
/// </summary>
public decimal RiskReward
{
get => _riskReward.Value;
set => _riskReward.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FvgPositioningAverageWith200EmaAutoTradingStrategy()
{
_fvgLookback = Param(nameof(FvgLookback), 30)
.SetGreaterThanZero()
.SetDisplay("FVG Lookback", "Number of recent gaps of each direction that are averaged", "FVG");
_atrMultiplier = Param(nameof(AtrMultiplier), 0.25m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "Minimum gap size in ATRs", "FVG");
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Candles used for the stop at the recent low or high", "Risk");
_emaPeriod = Param(nameof(EmaPeriod), 200)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Trend EMA period", "Indicators");
_riskReward = Param(nameof(RiskReward), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Risk Reward", "Target distance as a multiple of the stop distance", "Risk");
_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();
ResetState();
}
private void ResetState()
{
_bullLevels.Clear();
_bearLevels.Clear();
_high1 = _high2 = _low1 = _low2 = null;
_prevClose = _prevBullAvg = _prevBearAvg = null;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var atr = new AverageTrueRange { Length = _atrLength };
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var highest = new Highest { Length = LookbackPeriod };
var lowest = new Lowest { Length = LookbackPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atr, decimal ema, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished)
return;
if (_high2 is decimal high2 && _low2 is decimal low2)
{
var minGap = atr * AtrMultiplier;
if (candle.LowPrice > high2 && candle.LowPrice - high2 > minGap)
AddLevel(_bullLevels, candle.LowPrice);
else if (candle.HighPrice < low2 && low2 - candle.HighPrice > minGap)
AddLevel(_bearLevels, candle.HighPrice);
}
_high2 = _high1;
_low2 = _low1;
_high1 = candle.HighPrice;
_low1 = candle.LowPrice;
decimal? bullAvg = _bullLevels.Count > 0 ? _bullLevels.Average() : null;
decimal? bearAvg = _bearLevels.Count > 0 ? _bearLevels.Average() : null;
var prevClose = _prevClose;
var prevBullAvg = _prevBullAvg;
var prevBearAvg = _prevBearAvg;
_prevClose = candle.ClosePrice;
_prevBullAvg = bullAvg;
_prevBearAvg = bearAvg;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (ManagePosition(candle))
return;
if (bullAvg is not decimal bull || bearAvg is not decimal bear || prevClose is not decimal pc)
return;
var close = candle.ClosePrice;
var crossUp = prevBearAvg is decimal pBear && pc <= pBear && close > bear;
var crossDown = prevBullAvg is decimal pBull && pc >= pBull && close < bull;
if (crossUp && bull > ema && bear > ema && Position <= 0 && lowest < close)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = lowest;
_takePrice = close + (close - lowest) * RiskReward;
}
else if (crossDown && bull < ema && bear < ema && Position >= 0 && highest > close)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = highest;
_takePrice = close - (highest - close) * RiskReward;
}
}
private void AddLevel(List<decimal> levels, decimal level)
{
levels.Add(level);
while (levels.Count > FvgLookback)
levels.RemoveAt(0);
}
// Returns true when the position was closed on this candle.
private bool ManagePosition(ICandleMessage candle)
{
if (Position > 0 && _stopPrice > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
{
SellMarket(Position);
return true;
}
}
else if (Position < 0 && _stopPrice > 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)
{
BuyMarket(Math.Abs(Position));
return true;
}
}
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 AverageTrueRange, ExponentialMovingAverage, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class fvg_positioning_average_with_200ema_auto_trading_strategy(Strategy):
"""
FVG positioning average with 200 EMA strategy.
A bullish fair value gap is a candle whose low is above the high two candles back by more than AtrMultiplier ATRs, a bearish gap
the mirror. The levels of the last FvgLookback bullish and bearish gaps are averaged. A close crossing above the bearish average
while both averages are above the EMA goes long, a close crossing below the bullish average while both are below the EMA goes short.
The stop sits at the lowest low (highest high) of LookbackPeriod candles and the target at RiskReward times that risk.
"""
ATR_LENGTH = 200
def __init__(self):
super(fvg_positioning_average_with_200ema_auto_trading_strategy, self).__init__()
self._fvg_lookback = self.Param("FvgLookback", 30).SetGreaterThanZero().SetDisplay("FVG Lookback", "Number of recent gaps of each direction that are averaged", "FVG")
self._atr_multiplier = self.Param("AtrMultiplier", 0.25).SetNotNegative().SetDisplay("ATR Multiplier", "Minimum gap size in ATRs", "FVG")
self._lookback_period = self.Param("LookbackPeriod", 20).SetGreaterThanZero().SetDisplay("Lookback Period", "Candles used for the stop at the recent low or high", "Risk")
self._ema_period = self.Param("EmaPeriod", 200).SetGreaterThanZero().SetDisplay("EMA Period", "Trend EMA period", "Indicators")
self._risk_reward = self.Param("RiskReward", 1.5).SetGreaterThanZero().SetDisplay("Risk Reward", "Target distance as a multiple of the stop distance", "Risk")
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._bull_levels = []
self._bear_levels = []
self._high1 = None
self._high2 = None
self._low1 = None
self._low2 = None
self._prev_close = None
self._prev_bull_avg = None
self._prev_bear_avg = None
self._stop_price = Decimal(0)
self._take_price = Decimal(0)
def OnReseted(self):
super(fvg_positioning_average_with_200ema_auto_trading_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(fvg_positioning_average_with_200ema_auto_trading_strategy, self).OnStarted2(time)
self._reset_state()
atr = AverageTrueRange()
atr.Length = self.ATR_LENGTH
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
highest = Highest()
highest.Length = self._lookback_period.Value
lowest = Lowest()
lowest.Length = self._lookback_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, ema, highest, lowest, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _add_level(self, levels, level):
levels.append(level)
while len(levels) > self._fvg_lookback.Value:
levels.pop(0)
def _process_candle(self, candle, atr, ema, highest, lowest):
if candle.State != CandleStates.Finished:
return
if self._high2 is not None and self._low2 is not None:
min_gap = atr * Decimal(self._atr_multiplier.Value)
if candle.LowPrice > self._high2 and candle.LowPrice - self._high2 > min_gap:
self._add_level(self._bull_levels, candle.LowPrice)
elif candle.HighPrice < self._low2 and self._low2 - candle.HighPrice > min_gap:
self._add_level(self._bear_levels, candle.HighPrice)
self._high2 = self._high1
self._low2 = self._low1
self._high1 = candle.HighPrice
self._low1 = candle.LowPrice
bull_avg = sum(self._bull_levels, Decimal(0)) / Decimal(len(self._bull_levels)) if self._bull_levels else None
bear_avg = sum(self._bear_levels, Decimal(0)) / Decimal(len(self._bear_levels)) if self._bear_levels else None
prev_close = self._prev_close
prev_bull_avg = self._prev_bull_avg
prev_bear_avg = self._prev_bear_avg
self._prev_close = candle.ClosePrice
self._prev_bull_avg = bull_avg
self._prev_bear_avg = bear_avg
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._manage_position(candle):
return
if bull_avg is None or bear_avg is None or prev_close is None:
return
close = candle.ClosePrice
rr = Decimal(self._risk_reward.Value)
cross_up = prev_bear_avg is not None and prev_close <= prev_bear_avg and close > bear_avg
cross_down = prev_bull_avg is not None and prev_close >= prev_bull_avg and close < bull_avg
if cross_up and bull_avg > ema and bear_avg > ema and self.Position <= 0 and lowest < close:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = lowest
self._take_price = close + (close - lowest) * rr
elif cross_down and bull_avg < ema and bear_avg < ema and self.Position >= 0 and highest > close:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = highest
self._take_price = close - (highest - close) * rr
def _manage_position(self, candle):
if self.Position > 0 and self._stop_price > 0:
if candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price:
self.SellMarket(self.Position)
return True
elif self.Position < 0 and self._stop_price > 0:
if candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price:
self.BuyMarket(abs(self.Position))
return True
return False
def CreateClone(self):
return fvg_positioning_average_with_200ema_auto_trading_strategy()