Angry Bird スキャルピング戦略
この戦略はStockSharpの高レベルAPIを使用してMetaTraderのエキスパートアドバイザー「Angry Bird (Scalping)」を再現します。
ロジック
- 15分足ローソク足を観察し、最後の
Depthバーにわたる最高値と最安値を計算して動的グリッドステップを導出します。 - ポジションが開いておらず、前のローソク足が現在のものより高く終値した場合、時間足のRSIがエントリーを引き起こします:
RsiMinより高い値はショートポジションを開き、RsiMaxより低い値はロングポジションを開きます。 - ポジションが存在し、価格が少なくともグリッドステップ分逆行した場合、
MaxTradesに達するまで同じ方向にLotExponentを掛けたボリュームで新しいポジションが開かれます。 - ショートに対して
CciDropを超える、またはロングに対して-CciDropを下回る強いCCI値はすべてのポジションを強制決済します。 - 平均エントリー価格に対して利益が
TakeProfitまたは損失がStopLossに達した時もポジションを決済します。
パラメーター
StopLoss– ポイント単位のストップロス。TakeProfit– ポイント単位のテイクプロフィット。DefaultPips– グリッド注文間の最小距離(pips)。Depth– 高値/安値計算に使用するローソク足の数。LotExponent– 後続注文ボリュームの乗数。MaxTrades– 平均化ポジションの最大数。RsiMin/RsiMax– エントリーのRSIしきい値。CciDrop– ポジション決済を強制するCCIの絶対値。Volume– 初期注文ボリューム。CandleType– 作業ローソク足の時間軸(デフォルト15分)。
使用方法
戦略を銘柄に接続して開始します。戦略は成行注文を使用し、価格が逆行した時に平均化しながら単一のネットポジションを管理します。
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Angry Bird scalping strategy.
/// Uses RSI and CCI indicators with a dynamic grid for averaging positions.
/// </summary>
public class AngryBirdScalpingStrategy : Strategy
{
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _defaultPips;
private readonly StrategyParam<int> _depth;
private readonly StrategyParam<decimal> _lotExponent;
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<decimal> _rsiMin;
private readonly StrategyParam<decimal> _rsiMax;
private readonly StrategyParam<decimal> _cciDrop;
private readonly StrategyParam<DataType> _candleType;
private decimal _lastOpenBuyPrice;
private decimal _lastOpenSellPrice;
private decimal _entryPrice;
private int _tradeCount;
private bool _longTrade;
private bool _shortTrade;
private decimal _rsiValue;
private decimal? _prevClose;
public int StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public int TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public int DefaultPips { get => _defaultPips.Value; set => _defaultPips.Value = value; }
public int Depth { get => _depth.Value; set => _depth.Value = value; }
public decimal LotExponent { get => _lotExponent.Value; set => _lotExponent.Value = value; }
public int MaxTrades { get => _maxTrades.Value; set => _maxTrades.Value = value; }
public decimal RsiMin { get => _rsiMin.Value; set => _rsiMin.Value = value; }
public decimal RsiMax { get => _rsiMax.Value; set => _rsiMax.Value = value; }
public decimal CciDrop { get => _cciDrop.Value; set => _cciDrop.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AngryBirdScalpingStrategy()
{
_stopLoss = Param(nameof(StopLoss), 500)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss in points", "Risk");
_takeProfit = Param(nameof(TakeProfit), 40)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit in points", "Risk");
_defaultPips = Param(nameof(DefaultPips), 20)
.SetGreaterThanZero()
.SetDisplay("Default Pips", "Minimal grid step in pips", "Grid");
_depth = Param(nameof(Depth), 24)
.SetGreaterThanZero()
.SetDisplay("Depth", "Bars for high/low calculation", "Grid");
_lotExponent = Param(nameof(LotExponent), 1.62m)
.SetGreaterThanZero()
.SetDisplay("Lot Exponent", "Volume multiplier for averaging", "Grid");
_maxTrades = Param(nameof(MaxTrades), 3)
.SetGreaterThanZero()
.SetDisplay("Max Trades", "Maximum number of averaging orders", "Grid");
_rsiMin = Param(nameof(RsiMin), 70m)
.SetDisplay("RSI Min", "RSI threshold to sell", "Signals");
_rsiMax = Param(nameof(RsiMax), 30m)
.SetDisplay("RSI Max", "RSI threshold to buy", "Signals");
_cciDrop = Param(nameof(CciDrop), 500m)
.SetGreaterThanZero()
.SetDisplay("CCI Drop", "CCI value to close positions", "Signals");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Working candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastOpenBuyPrice = 0m;
_lastOpenSellPrice = 0m;
_entryPrice = 0m;
_tradeCount = 0;
_longTrade = false;
_shortTrade = false;
_rsiValue = 0m;
_prevClose = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_tradeCount = 0;
_longTrade = false;
_shortTrade = false;
_entryPrice = 0;
var cci = new CommodityChannelIndex { Length = 55 };
var rsi = new RelativeStrengthIndex { Length = 14 };
var highest = new Highest { Length = Depth };
var lowest = new Lowest { Length = Depth };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(cci, rsi, highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, cci);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cci, decimal rsi, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var stepPrice = Security.PriceStep ?? 1m;
var pipDistance = Math.Max((highest - lowest) / Math.Max(stepPrice, 1m), DefaultPips) * stepPrice;
_rsiValue = rsi;
// Close all positions on strong CCI movement
if ((cci > CciDrop && _shortTrade) || (cci < -CciDrop && _longTrade))
{
CloseAll();
return;
}
var tradeNow = false;
if (Position == 0m)
{
_tradeCount = 0;
_longTrade = false;
_shortTrade = false;
tradeNow = true;
}
else if (_tradeCount < MaxTrades)
{
if (_longTrade && _lastOpenBuyPrice - close >= pipDistance)
tradeNow = true;
if (_shortTrade && close - _lastOpenSellPrice >= pipDistance)
tradeNow = true;
}
if (tradeNow)
{
var volume = Volume * (decimal)Math.Pow((double)LotExponent, _tradeCount);
if (_longTrade)
{
BuyMarket(volume);
_lastOpenBuyPrice = close;
_tradeCount++;
}
else if (_shortTrade)
{
SellMarket(volume);
_lastOpenSellPrice = close;
_tradeCount++;
}
else if (_prevClose is decimal prev && prev > close)
{
if (_rsiValue > RsiMin)
{
SellMarket(volume);
_shortTrade = true;
_lastOpenSellPrice = close;
_entryPrice = close;
_tradeCount = 1;
}
else if (_rsiValue < RsiMax)
{
BuyMarket(volume);
_longTrade = true;
_lastOpenBuyPrice = close;
_entryPrice = close;
_tradeCount = 1;
}
}
}
if (Position != 0m)
{
if (_longTrade)
{
var tp = _entryPrice + TakeProfit * stepPrice;
var sl = _entryPrice - StopLoss * stepPrice;
if (close >= tp || close <= sl)
CloseAll();
}
else if (_shortTrade)
{
var tp = _entryPrice - TakeProfit * stepPrice;
var sl = _entryPrice + StopLoss * stepPrice;
if (close <= tp || close >= sl)
CloseAll();
}
}
_prevClose = close;
}
private void CloseAll()
{
if (Position > 0)
SellMarket(Math.Abs(Position));
else if (Position < 0)
BuyMarket(Math.Abs(Position));
_tradeCount = 0;
_longTrade = false;
_shortTrade = false;
_entryPrice = 0;
}
}
import clr
import math
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 CommodityChannelIndex, Highest, Lowest, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class angry_bird_scalping_strategy(Strategy):
def __init__(self):
super(angry_bird_scalping_strategy, self).__init__()
self._stop_loss = self.Param("StopLoss", 500)
self._take_profit = self.Param("TakeProfit", 40)
self._default_pips = self.Param("DefaultPips", 20)
self._depth = self.Param("Depth", 24)
self._lot_exponent = self.Param("LotExponent", 1.62)
self._max_trades = self.Param("MaxTrades", 3)
self._rsi_min = self.Param("RsiMin", 70.0)
self._rsi_max = self.Param("RsiMax", 30.0)
self._cci_drop = self.Param("CciDrop", 500.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._last_open_buy_price = 0.0
self._last_open_sell_price = 0.0
self._entry_price = 0.0
self._trade_count = 0
self._long_trade = False
self._short_trade = False
self._rsi_value = 0.0
self._prev_close = None
@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(angry_bird_scalping_strategy, self).OnStarted2(time)
self._trade_count = 0
self._long_trade = False
self._short_trade = False
self._entry_price = 0.0
self._last_open_buy_price = 0.0
self._last_open_sell_price = 0.0
self._rsi_value = 0.0
self._prev_close = None
cci = CommodityChannelIndex()
cci.Length = 55
rsi = RelativeStrengthIndex()
rsi.Length = 14
highest = Highest()
highest.Length = int(self._depth.Value)
lowest = Lowest()
lowest.Length = int(self._depth.Value)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, rsi, highest, lowest, self.ProcessCandle).Start()
def ProcessCandle(self, candle, cci_value, rsi_value, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
cci_val = float(cci_value)
rsi_val = float(rsi_value)
high_val = float(highest_value)
low_val = float(lowest_value)
step_price = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
pip_distance = max((high_val - low_val) / max(step_price, 1.0), float(self._default_pips.Value)) * step_price
self._rsi_value = rsi_val
cci_drop = float(self._cci_drop.Value)
if (cci_val > cci_drop and self._short_trade) or (cci_val < -cci_drop and self._long_trade):
self._close_all()
return
trade_now = False
pos = float(self.Position)
if pos == 0:
self._trade_count = 0
self._long_trade = False
self._short_trade = False
trade_now = True
elif self._trade_count < int(self._max_trades.Value):
if self._long_trade and self._last_open_buy_price - close >= pip_distance:
trade_now = True
if self._short_trade and close - self._last_open_sell_price >= pip_distance:
trade_now = True
if trade_now:
vol = float(self.Volume) * math.pow(float(self._lot_exponent.Value), self._trade_count)
if self._long_trade:
self.BuyMarket(vol)
self._last_open_buy_price = close
self._trade_count += 1
elif self._short_trade:
self.SellMarket(vol)
self._last_open_sell_price = close
self._trade_count += 1
elif self._prev_close is not None and self._prev_close > close:
rsi_min = float(self._rsi_min.Value)
rsi_max = float(self._rsi_max.Value)
if self._rsi_value > rsi_min:
self.SellMarket(vol)
self._short_trade = True
self._last_open_sell_price = close
self._entry_price = close
self._trade_count = 1
elif self._rsi_value < rsi_max:
self.BuyMarket(vol)
self._long_trade = True
self._last_open_buy_price = close
self._entry_price = close
self._trade_count = 1
pos = float(self.Position)
if pos != 0:
sl = float(self._stop_loss.Value)
tp = float(self._take_profit.Value)
if self._long_trade:
tp_price = self._entry_price + tp * step_price
sl_price = self._entry_price - sl * step_price
if close >= tp_price or close <= sl_price:
self._close_all()
elif self._short_trade:
tp_price = self._entry_price - tp * step_price
sl_price = self._entry_price + sl * step_price
if close <= tp_price or close >= sl_price:
self._close_all()
self._prev_close = close
def _close_all(self):
pos = float(self.Position)
if pos > 0:
self.SellMarket(abs(pos))
elif pos < 0:
self.BuyMarket(abs(pos))
self._trade_count = 0
self._long_trade = False
self._short_trade = False
self._entry_price = 0.0
def OnReseted(self):
super(angry_bird_scalping_strategy, self).OnReseted()
self._last_open_buy_price = 0.0
self._last_open_sell_price = 0.0
self._entry_price = 0.0
self._trade_count = 0
self._long_trade = False
self._short_trade = False
self._rsi_value = 0.0
self._prev_close = None
def CreateClone(self):
return angry_bird_scalping_strategy()