Angrybird xScalpingn
Angrybird xScalpingn 是一种马丁格尔式剥头皮策略。首笔交易根据最近的收盘方向和 RSI 过滤器入场。当价格逆着持仓按最近区间计算的动态步长移动时,策略会按系数放大手数加仓。CCI 出现强烈反向信号或触发止损/止盈时,所有仓位都会平掉。
细节
- 入场条件:首笔交易跟随最近的收盘方向并满足 RSI 过滤。价格逆势达到计算步长时加仓。
- 多空:双向。
- 出场条件:CCI 反向或保护性止损/止盈。
- 止损:有。
- 默认值:
Volume= 0.01LotExponent= 2DynamicPips= trueDefaultPips= 12Depth= 24Del= 3TakeProfit= 20StopLoss= 500Drop= 500RsiMinimum= 30RsiMaximum= 70MaxTrades= 10CandleType= TimeSpan.FromMinutes(1)
- 过滤器:
- 类别: Grid
- 方向: 双向
- 指标: RSI, CCI
- 止损: 有
- 复杂度: 高
- 时间框架: 任意
- 季节性: 无
- 神经网络: 无
- 背离: 无
- 风险等级: 高
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
public class AngrybirdXScalpingnStrategy : Strategy
{
private readonly StrategyParam<decimal> _lotExponent;
private readonly StrategyParam<bool> _dynamicPips;
private readonly StrategyParam<int> _defaultPips;
private readonly StrategyParam<int> _depth;
private readonly StrategyParam<int> _del;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _drop;
private readonly StrategyParam<decimal> _rsiMinimum;
private readonly StrategyParam<decimal> _rsiMaximum;
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _highs = new();
private readonly Queue<decimal> _lows = new();
private decimal? _lastBuyPrice;
private decimal? _lastSellPrice;
private int _tradeCount;
private decimal _pipStep;
private decimal _prevClose;
public decimal LotExponent { get => _lotExponent.Value; set => _lotExponent.Value = value; }
public bool DynamicPips { get => _dynamicPips.Value; set => _dynamicPips.Value = value; }
public int DefaultPips { get => _defaultPips.Value; set => _defaultPips.Value = value; }
public int Depth { get => _depth.Value; set => _depth.Value = value; }
public int Del { get => _del.Value; set => _del.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal Drop { get => _drop.Value; set => _drop.Value = value; }
public decimal RsiMinimum { get => _rsiMinimum.Value; set => _rsiMinimum.Value = value; }
public decimal RsiMaximum { get => _rsiMaximum.Value; set => _rsiMaximum.Value = value; }
public int MaxTrades { get => _maxTrades.Value; set => _maxTrades.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AngrybirdXScalpingnStrategy()
{
_lotExponent = Param(nameof(LotExponent), 2m)
.SetDisplay("Lot Exponent", "Volume multiplier for additional trades", "General")
.SetGreaterThanZero();
_dynamicPips = Param(nameof(DynamicPips), false)
.SetDisplay("Dynamic Pips", "Use dynamic grid step", "Parameters");
_defaultPips = Param(nameof(DefaultPips), 12)
.SetDisplay("Default Pips", "Base grid step in ticks", "Parameters")
.SetGreaterThanZero();
_depth = Param(nameof(Depth), 24)
.SetDisplay("Depth", "Bars lookback for dynamic step", "Parameters")
.SetGreaterThanZero();
_del = Param(nameof(Del), 3)
.SetDisplay("Del", "Divider for range calculation", "Parameters")
.SetGreaterThanZero();
_takeProfit = Param(nameof(TakeProfit), 20m)
.SetDisplay("Take Profit", "Take profit in ticks", "Risk")
.SetNotNegative();
_stopLoss = Param(nameof(StopLoss), 500m)
.SetDisplay("Stop Loss", "Stop loss in ticks", "Risk")
.SetNotNegative();
_drop = Param(nameof(Drop), 500m)
.SetDisplay("CCI Drop", "CCI threshold for exit", "Parameters")
.SetNotNegative();
_rsiMinimum = Param(nameof(RsiMinimum), 30m)
.SetDisplay("RSI Minimum", "Minimum RSI to allow short", "Parameters")
.SetNotNegative();
_rsiMaximum = Param(nameof(RsiMaximum), 70m)
.SetDisplay("RSI Maximum", "Maximum RSI to allow long", "Parameters")
.SetNotNegative();
_maxTrades = Param(nameof(MaxTrades), 2)
.SetDisplay("Max Trades", "Maximum number of open trades", "Risk")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "Parameters");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_lastBuyPrice = _lastSellPrice = null;
_tradeCount = 0;
_pipStep = 0m;
_prevClose = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = 14 };
var cci = new CommodityChannelIndex { Length = 55 };
var sub = SubscribeCandles(CandleType);
sub.Bind(rsi, cci, ProcessCandle)
.Start();
StartProtection(new Unit(TakeProfit, UnitTypes.Absolute), new Unit(StopLoss, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, sub);
DrawIndicator(area, rsi);
DrawIndicator(area, cci);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal cci)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
// calculate dynamic grid step
if (DynamicPips)
{
_highs.Enqueue(high);
_lows.Enqueue(low);
if (_highs.Count > Depth)
{
_highs.Dequeue();
_lows.Dequeue();
}
if (_highs.Count == Depth)
{
var highs = _highs.ToArray();
var lows = _lows.ToArray();
var highest = highs.Max();
var lowest = lows.Min();
var step = (highest - lowest) / Del;
var stepSize = Security?.PriceStep ?? 1m;
var minStep = (DefaultPips / (decimal)Del) * stepSize;
var maxStep = (DefaultPips * Del) * stepSize;
step = Math.Min(Math.Max(step, minStep), maxStep);
_pipStep = step;
}
}
else
{
_pipStep = DefaultPips * (Security?.PriceStep ?? 1m);
}
if (Position == 0)
{
_lastBuyPrice = _lastSellPrice = null;
_tradeCount = 0;
}
// close all on CCI reversal
if (Position > 0 && cci < -Drop)
{
if (Position > 0) SellMarket(Math.Abs(Position));
else if (Position < 0) BuyMarket(Math.Abs(Position));
return;
}
if (Position < 0 && cci > Drop)
{
if (Position > 0) SellMarket(Math.Abs(Position));
else if (Position < 0) BuyMarket(Math.Abs(Position));
return;
}
if (Position != 0)
{
if (_tradeCount < MaxTrades)
{
if (Position > 0 && _lastBuyPrice is decimal lb && lb - close >= _pipStep)
{
var vol = Volume * (decimal)Math.Pow((double)LotExponent, _tradeCount);
BuyMarket(vol);
_lastBuyPrice = close;
_tradeCount++;
}
else if (Position < 0 && _lastSellPrice is decimal ls && close - ls >= _pipStep)
{
var vol = Volume * (decimal)Math.Pow((double)LotExponent, _tradeCount);
SellMarket(vol);
_lastSellPrice = close;
_tradeCount++;
}
}
_prevClose = close;
return;
}
// first trade decision
if (_prevClose != 0m)
{
if (_prevClose > close && rsi > RsiMinimum)
{
SellMarket(Volume);
_lastSellPrice = close;
_tradeCount = 1;
}
else if (_prevClose <= close && rsi < RsiMaximum)
{
BuyMarket(Volume);
_lastBuyPrice = close;
_tradeCount = 1;
}
}
_prevClose = close;
}
}
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 collections import deque
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import CommodityChannelIndex, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class angrybird_x_scalpingn_strategy(Strategy):
def __init__(self):
super(angrybird_x_scalpingn_strategy, self).__init__()
self._lot_exponent = self.Param("LotExponent", 2.0) \
.SetDisplay("Lot Exponent", "Volume multiplier for additional trades", "General")
self._dynamic_pips = self.Param("DynamicPips", False) \
.SetDisplay("Dynamic Pips", "Use dynamic grid step", "Parameters")
self._default_pips = self.Param("DefaultPips", 12) \
.SetDisplay("Default Pips", "Base grid step in ticks", "Parameters")
self._depth = self.Param("Depth", 24) \
.SetDisplay("Depth", "Bars lookback for dynamic step", "Parameters")
self._del = self.Param("Del", 3) \
.SetDisplay("Del", "Divider for range calculation", "Parameters")
self._take_profit = self.Param("TakeProfit", 20.0) \
.SetDisplay("Take Profit", "Take profit in ticks", "Risk")
self._stop_loss = self.Param("StopLoss", 500.0) \
.SetDisplay("Stop Loss", "Stop loss in ticks", "Risk")
self._drop = self.Param("Drop", 500.0) \
.SetDisplay("CCI Drop", "CCI threshold for exit", "Parameters")
self._rsi_minimum = self.Param("RsiMinimum", 30.0) \
.SetDisplay("RSI Minimum", "Minimum RSI to allow short", "Parameters")
self._rsi_maximum = self.Param("RsiMaximum", 70.0) \
.SetDisplay("RSI Maximum", "Maximum RSI to allow long", "Parameters")
self._max_trades = self.Param("MaxTrades", 2) \
.SetDisplay("Max Trades", "Maximum number of open trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "Parameters")
self._highs = deque()
self._lows = deque()
self._last_buy_price = None
self._last_sell_price = None
self._trade_count = 0
self._pip_step = 0.0
self._prev_close = 0.0
@property
def LotExponent(self):
return self._lot_exponent.Value
@LotExponent.setter
def LotExponent(self, value):
self._lot_exponent.Value = value
@property
def DynamicPips(self):
return self._dynamic_pips.Value
@DynamicPips.setter
def DynamicPips(self, value):
self._dynamic_pips.Value = value
@property
def DefaultPips(self):
return self._default_pips.Value
@DefaultPips.setter
def DefaultPips(self, value):
self._default_pips.Value = value
@property
def Depth(self):
return self._depth.Value
@Depth.setter
def Depth(self, value):
self._depth.Value = value
@property
def Del(self):
return self._del.Value
@Del.setter
def Del(self, value):
self._del.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def Drop(self):
return self._drop.Value
@Drop.setter
def Drop(self, value):
self._drop.Value = value
@property
def RsiMinimum(self):
return self._rsi_minimum.Value
@RsiMinimum.setter
def RsiMinimum(self, value):
self._rsi_minimum.Value = value
@property
def RsiMaximum(self):
return self._rsi_maximum.Value
@RsiMaximum.setter
def RsiMaximum(self, value):
self._rsi_maximum.Value = value
@property
def MaxTrades(self):
return self._max_trades.Value
@MaxTrades.setter
def MaxTrades(self, value):
self._max_trades.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(angrybird_x_scalpingn_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = 14
cci = CommodityChannelIndex()
cci.Length = 55
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(rsi, cci, self.ProcessCandle).Start()
self.StartProtection(
Unit(self.TakeProfit, UnitTypes.Absolute),
Unit(self.StopLoss, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, rsi)
self.DrawIndicator(area, cci)
def ProcessCandle(self, candle, rsi, cci):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
rsi_val = float(rsi)
cci_val = float(cci)
if self.DynamicPips:
self._highs.append(high)
self._lows.append(low)
if len(self._highs) > self.Depth:
self._highs.popleft()
self._lows.popleft()
if len(self._highs) == self.Depth:
highest = max(self._highs)
lowest = min(self._lows)
step_val = (highest - lowest) / float(self.Del)
step_raw = self.Security.PriceStep
step_size = float(step_raw) if step_raw is not None else 1.0
min_step = (float(self.DefaultPips) / float(self.Del)) * step_size
max_step = (float(self.DefaultPips) * float(self.Del)) * step_size
step_val = min(max(step_val, min_step), max_step)
self._pip_step = step_val
else:
step_raw = self.Security.PriceStep
step_size = float(step_raw) if step_raw is not None else 1.0
self._pip_step = float(self.DefaultPips) * step_size
if self.Position == 0:
self._last_buy_price = None
self._last_sell_price = None
self._trade_count = 0
if self.Position > 0 and cci_val < -float(self.Drop):
if self.Position > 0:
self.SellMarket(abs(self.Position))
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
return
if self.Position < 0 and cci_val > float(self.Drop):
if self.Position > 0:
self.SellMarket(abs(self.Position))
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
return
if self.Position != 0:
if self._trade_count < self.MaxTrades:
if (self.Position > 0
and self._last_buy_price is not None
and self._last_buy_price - close >= self._pip_step):
vol = self.Volume * (float(self.LotExponent) ** self._trade_count)
self.BuyMarket(vol)
self._last_buy_price = close
self._trade_count += 1
elif (self.Position < 0
and self._last_sell_price is not None
and close - self._last_sell_price >= self._pip_step):
vol = self.Volume * (float(self.LotExponent) ** self._trade_count)
self.SellMarket(vol)
self._last_sell_price = close
self._trade_count += 1
self._prev_close = close
return
if self._prev_close != 0.0:
if self._prev_close > close and rsi_val > float(self.RsiMinimum):
self.SellMarket(self.Volume)
self._last_sell_price = close
self._trade_count = 1
elif self._prev_close <= close and rsi_val < float(self.RsiMaximum):
self.BuyMarket(self.Volume)
self._last_buy_price = close
self._trade_count = 1
self._prev_close = close
def OnReseted(self):
super(angrybird_x_scalpingn_strategy, self).OnReseted()
self._highs = deque()
self._lows = deque()
self._last_buy_price = None
self._last_sell_price = None
self._trade_count = 0
self._pip_step = 0.0
self._prev_close = 0.0
def CreateClone(self):
return angrybird_x_scalpingn_strategy()