Ver no GitHub

Angrybird xScalpingn

Angrybird xScalpingn is a martingale-style scalping strategy. It opens an initial trade based on short-term price direction and an RSI filter. When price moves against the open position by a dynamic step derived from recent range, the strategy adds another trade with volume multiplied by a factor. All positions are closed when CCI shows a strong counter move or when stop loss or take profit is hit.

Details

  • Entry Criteria: Initial trade follows the recent close direction with an RSI filter. Additional trades are opened when price moves against the position by the calculated step.
  • Long/Short: Both directions.
  • Exit Criteria: CCI reversal or protective stop loss/take profit.
  • Stops: Yes.
  • Default Values:
    • Volume = 0.01
    • LotExponent = 2
    • DynamicPips = true
    • DefaultPips = 12
    • Depth = 24
    • Del = 3
    • TakeProfit = 20
    • StopLoss = 500
    • Drop = 500
    • RsiMinimum = 30
    • RsiMaximum = 70
    • MaxTrades = 10
    • CandleType = TimeSpan.FromMinutes(1)
  • Filters:
    • Category: Grid
    • Direction: Both
    • Indicators: RSI, CCI
    • Stops: Yes
    • Complexity: Advanced
    • Timeframe: Any
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: High
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;
	}
}