镊子顶策略
镊子顶形态出现在行情上涨后,两根蜡烛具有几乎相同的最高价,显示买方力量受阻。 当第二根蜡烛确认顶部时,策略做空,期望上涨动能减弱后出现回落。 在双重高点上方设置紧密止损控制风险,如果价格重新突破该阻力则退出。
测试表明年均收益约为 187%,该策略在股票市场表现最佳。
细节
- 入场条件:形态匹配
- 多/空:均可
- 退出条件:止损或反向信号
- 止损:是,按百分比
- 默认值:
CandleType= 15分钟StopLoss= 2%
- 过滤器:
- 类别:形态
- 方向:双向
- 指标:蜡烛图
- 止损:有
- 复杂度:中等
- 时间框架:日内
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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>
/// Tweezer Top strategy.
/// Enters short on Tweezer Top (bullish then bearish with matching highs).
/// Enters long on Tweezer Bottom (bearish then bullish with matching lows).
/// Uses SMA for exit confirmation.
/// Uses cooldown to control trade frequency.
/// </summary>
public class TweezerTopStrategy : Strategy
{
private readonly StrategyParam<decimal> _tolerancePercent;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private ICandleMessage _prevCandle;
private int _cooldown;
/// <summary>
/// Tolerance for matching highs/lows.
/// </summary>
public decimal TolerancePercent
{
get => _tolerancePercent.Value;
set => _tolerancePercent.Value = value;
}
/// <summary>
/// MA period for exit.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.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 TweezerTopStrategy()
{
_tolerancePercent = Param(nameof(TolerancePercent), 0.1m)
.SetRange(0.05m, 1m)
.SetDisplay("Tolerance %", "Max diff between highs/lows", "Pattern");
_maLength = Param(nameof(MaLength), 20)
.SetRange(10, 50)
.SetDisplay("MA Length", "Period of SMA for 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();
_prevCandle = null;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCandle = null;
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MaLength };
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;
if (_prevCandle == null)
{
_prevCandle = candle;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevCandle = candle;
return;
}
var highTolerance = _prevCandle.HighPrice * (TolerancePercent / 100m);
var lowTolerance = _prevCandle.LowPrice * (TolerancePercent / 100m);
// Tweezer Top: prev bullish, current bearish, matching highs
var isTweezerTop =
_prevCandle.ClosePrice > _prevCandle.OpenPrice &&
candle.ClosePrice < candle.OpenPrice &&
Math.Abs(_prevCandle.HighPrice - candle.HighPrice) <= highTolerance;
// Tweezer Bottom: prev bearish, current bullish, matching lows
var isTweezerBottom =
_prevCandle.ClosePrice < _prevCandle.OpenPrice &&
candle.ClosePrice > candle.OpenPrice &&
Math.Abs(_prevCandle.LowPrice - candle.LowPrice) <= lowTolerance;
if (Position == 0 && isTweezerTop)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && isTweezerBottom)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
_prevCandle = candle;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class tweezer_top_strategy(Strategy):
"""
Tweezer Top strategy.
Enters short on Tweezer Top (bullish then bearish with matching highs).
Enters long on Tweezer Bottom (bearish then bullish with matching lows).
Uses SMA for exit confirmation.
"""
def __init__(self):
super(tweezer_top_strategy, self).__init__()
self._tolerance_percent = self.Param("TolerancePercent", 0.1).SetDisplay("Tolerance %", "Max diff between highs/lows", "Pattern")
self._ma_length = self.Param("MaLength", 20).SetDisplay("MA Length", "Period of SMA for 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._prev_candle = None
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(tweezer_top_strategy, self).OnReseted()
self._prev_candle = None
self._cooldown = 0
def OnStarted2(self, time):
super(tweezer_top_strategy, self).OnStarted2(time)
self._prev_candle = None
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_length.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
if self._prev_candle is None:
self._prev_candle = candle
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_candle = candle
return
tol = self._tolerance_percent.Value
high_tolerance = float(self._prev_candle.HighPrice) * (tol / 100.0)
low_tolerance = float(self._prev_candle.LowPrice) * (tol / 100.0)
cd = self._cooldown_bars.Value
sv = float(sma_val)
# Tweezer Top: prev bullish, current bearish, matching highs
is_tweezer_top = (
self._prev_candle.ClosePrice > self._prev_candle.OpenPrice and
candle.ClosePrice < candle.OpenPrice and
abs(float(self._prev_candle.HighPrice) - float(candle.HighPrice)) <= high_tolerance
)
# Tweezer Bottom: prev bearish, current bullish, matching lows
is_tweezer_bottom = (
self._prev_candle.ClosePrice < self._prev_candle.OpenPrice and
candle.ClosePrice > candle.OpenPrice and
abs(float(self._prev_candle.LowPrice) - float(candle.LowPrice)) <= low_tolerance
)
if self.Position == 0 and is_tweezer_top:
self.SellMarket()
self._cooldown = cd
elif self.Position == 0 and is_tweezer_bottom:
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
self._prev_candle = candle
def CreateClone(self):
return tweezer_top_strategy()