上冲反转策略
上冲反转是弹簧形态的看跌对应,当价格短暂突破阻力后又迅速跌回。 这种走势会洗出追涨买家,随后往往向下反转。 当价格重新跌破突破位时,策略做空,预期供给会压倒需求。 止损放在上冲高点之上,若价格重新回到该水平则离场。
测试表明年均收益约为 58%,该策略在股票市场表现最佳。
细节
- 入场条件:指标信号
- 多/空:均可
- 退出条件:止损或反向信号
- 止损:是,按百分比
- 默认值:
CandleType= 15分钟StopLoss= 2%
- 过滤器:
- 类别:反转
- 方向:双向
- 指标:Wyckoff
- 止损:有
- 复杂度:中等
- 时间框架:日内
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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>
/// Upthrust Reversal strategy (Wyckoff).
/// Enters short when price spikes above recent resistance then closes back below it.
/// Enters long when price dips below recent support then closes back above it.
/// Uses SMA for exit confirmation.
/// Uses cooldown to control trade frequency.
/// </summary>
public class UpthrustReversalStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private int _cooldown;
/// <summary>
/// Lookback period.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// MA period for exit.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public UpthrustReversalStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetRange(5, 50)
.SetDisplay("Lookback", "Period for support/resistance", "Range");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetRange(5, 50)
.SetDisplay("MA Period", "Period for SMA exit", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highs.Clear();
_lows.Clear();
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > LookbackPeriod + 1)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count < LookbackPeriod + 1)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Find resistance and support from previous N bars
decimal resistance = decimal.MinValue;
decimal support = decimal.MaxValue;
for (int i = 0; i < _highs.Count - 1; i++)
{
if (_highs[i] > resistance) resistance = _highs[i];
if (_lows[i] < support) support = _lows[i];
}
// Upthrust: price spikes above resistance but closes below it (bearish)
var isUpthrust = candle.HighPrice > resistance && candle.ClosePrice < resistance && candle.ClosePrice < candle.OpenPrice;
// Spring: price dips below support but closes above it (bullish)
var isSpring = candle.LowPrice < support && candle.ClosePrice > support && candle.ClosePrice > candle.OpenPrice;
if (Position == 0 && isUpthrust)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && isSpring)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class upthrust_reversal_strategy(Strategy):
"""
Upthrust Reversal strategy (Wyckoff).
Enters short when price spikes above recent resistance then closes back below it.
Enters long when price dips below recent support then closes back above it.
Uses SMA for exit confirmation.
"""
def __init__(self):
super(upthrust_reversal_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 20).SetDisplay("Lookback", "Period for support/resistance", "Range")
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "Period for SMA exit", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._highs = []
self._lows = []
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(upthrust_reversal_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._cooldown = 0
def OnStarted2(self, time):
super(upthrust_reversal_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
lookback = self._lookback_period.Value
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
if len(self._highs) > lookback + 1:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < lookback + 1:
return
if self._cooldown > 0:
self._cooldown -= 1
return
cd = self._cooldown_bars.Value
sv = float(sma_val)
# Find resistance and support from previous N bars
resistance = max(self._highs[:-1])
support = min(self._lows[:-1])
# Upthrust: price spikes above resistance but closes below it (bearish)
is_upthrust = (
float(candle.HighPrice) > resistance and
float(candle.ClosePrice) < resistance and
candle.ClosePrice < candle.OpenPrice
)
# Spring: price dips below support but closes above it (bullish)
is_spring = (
float(candle.LowPrice) < support and
float(candle.ClosePrice) > support and
candle.ClosePrice > candle.OpenPrice
)
if self.Position == 0 and is_upthrust:
self.SellMarket()
self._cooldown = cd
elif self.Position == 0 and is_spring:
self.BuyMarket()
self._cooldown = cd
elif self.Position < 0 and float(candle.ClosePrice) > sv:
self.BuyMarket()
self._cooldown = cd
elif self.Position > 0 and float(candle.ClosePrice) < sv:
self.SellMarket()
self._cooldown = cd
def CreateClone(self):
return upthrust_reversal_strategy()