Ver en GitHub

Estrategia de Lanzamiento de Moneda

Descripción general

La Estrategia de Lanzamiento de Moneda elige aleatoriamente ir largo o corto en cada nueva vela cuando no hay posición abierta. Después de cerrar una posición, si la operación terminó en pérdida, el tamaño de la siguiente operación se incrementa usando un multiplicador de martingala. La estrategia cierra posiciones usando niveles fijos de take-profit y stop-loss definidos en pasos de precio y opcionalmente puede seguir las ganancias después de una distancia especificada.

Parámetros

  • Volume – tamaño base de la orden usado para el primer intento.
  • Martingale – multiplicador aplicado al volumen después de una operación perdedora.
  • MaxVolume – límite superior del tamaño de posición después de incrementos por martingala.
  • TakeProfit – objetivo de ganancia en pasos de precio.
  • StopLoss – límite de pérdida en pasos de precio.
  • TrailingStart – distancia en pasos de precio donde el trailing se activa.
  • TrailingStop – distancia del trailing stop en pasos de precio.
  • CandleType – marco temporal de las velas usado para la toma de decisiones.

Cómo funciona

  1. En cada vela completada, la estrategia verifica si hay una posición abierta.
  2. Si existe una posición, monitorea la ganancia o pérdida usando el precio de cierre actual. Una vez que se cumplen las condiciones de take-profit, stop-loss o trailing stop, la posición se cierra.
  3. Cuando no hay posición abierta, se lanza una moneda virtual:
    • Cara abre una posición larga.
    • Cruz abre una posición corta.
  4. Si la operación anterior fue una pérdida, el volumen se multiplica por Martingale pero con un límite máximo de MaxVolume.
  5. El trailing stop se activa una vez que el precio se mueve TrailingStart en la dirección favorable.

Notas

Este ejemplo está destinado a fines educativos para demostrar cómo trabajar con señales aleatorias y dimensionamiento de posiciones usando la API de alto nivel de StockSharp.

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;

/// <summary>
/// Strategy that randomly opens long or short positions.
/// Uses a martingale multiplier after losses and manages
/// take profit, stop loss and optional trailing stop in price steps.
/// </summary>
public class CoinFlipStrategy : Strategy
{
	private readonly StrategyParam<decimal> _martingale;
	private readonly StrategyParam<decimal> _maxVolume;
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;
	private readonly StrategyParam<int> _trailingStart;
	private readonly StrategyParam<int> _trailingStop;
	private readonly StrategyParam<DataType> _candleType;
	
	private static readonly Random _random = new();
	
	private decimal _entryPrice;
	private decimal _currentVolume;
	private decimal _trailingLevel;
	private bool _isLong;
	private bool _lastTradeLoss;
	
	
	/// <summary>
	/// Multiplier applied to volume after a losing trade.
	/// </summary>
	public decimal Martingale
	{
		get => _martingale.Value;
		set => _martingale.Value = value;
	}
	
	/// <summary>
	/// Maximum allowed volume after applying martingale.
	/// </summary>
	public decimal MaxVolume
	{
		get => _maxVolume.Value;
		set => _maxVolume.Value = value;
	}
	
	/// <summary>
	/// Take profit distance in price steps.
	/// </summary>
	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}
	
	/// <summary>
	/// Stop loss distance in price steps.
	/// </summary>
	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}
	
	/// <summary>
	/// Distance in price steps to activate trailing stop.
	/// </summary>
	public int TrailingStart
	{
		get => _trailingStart.Value;
		set => _trailingStart.Value = value;
	}
	
	/// <summary>
	/// Trailing stop distance in price steps.
	/// </summary>
	public int TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}
	
	/// <summary>
	/// Candle type for processing.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Constructor.
	/// </summary>
	public CoinFlipStrategy()
	{
		
		_martingale = Param(nameof(Martingale), 1.8m)
		.SetGreaterThanZero()
		.SetDisplay("Martingale", "Volume multiplier after loss", "General");
		
		_maxVolume = Param(nameof(MaxVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Max Volume", "Upper limit for volume", "General");
		
		_takeProfit = Param(nameof(TakeProfit), 50)
		.SetGreaterThanZero()
		.SetDisplay("Take Profit", "Profit target in steps", "Risk");
		
		_stopLoss = Param(nameof(StopLoss), 25)
		.SetGreaterThanZero()
		.SetDisplay("Stop Loss", "Loss limit in steps", "Risk");
		
		_trailingStart = Param(nameof(TrailingStart), 14)
		.SetNotNegative()
		.SetDisplay("Trailing Start", "Steps to activate trailing", "Risk");
		
		_trailingStop = Param(nameof(TrailingStop), 3)
		.SetNotNegative()
		.SetDisplay("Trailing Stop", "Trailing distance in steps", "Risk");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Time frame for analysis", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_currentVolume = 0;
		_trailingLevel = 0m;
		_isLong = false;
		_lastTradeLoss = false;
	}
	
	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		_currentVolume = Volume;
		
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
		
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}
	
	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position != 0)
		{
			if (_entryPrice == 0)
				return;

			// Manage open position using percent of entry
			var profitPct = _isLong
				? (candle.ClosePrice - _entryPrice) / _entryPrice * 100m
				: (_entryPrice - candle.ClosePrice) / _entryPrice * 100m;

			// Update trailing stop if in profit
			if (TrailingStart > 0 && profitPct >= TrailingStart * 0.1m)
			{
				var newLevel = _isLong
					? candle.ClosePrice * (1m - TrailingStop * 0.1m / 100m)
					: candle.ClosePrice * (1m + TrailingStop * 0.1m / 100m);

				if (_trailingLevel == 0m)
					_trailingLevel = newLevel;
				else if (_isLong && newLevel > _trailingLevel)
					_trailingLevel = newLevel;
				else if (!_isLong && newLevel < _trailingLevel)
					_trailingLevel = newLevel;
			}

			// Check exits - TP/SL as percentage (TakeProfit/10 %)
			if (profitPct >= TakeProfit * 0.1m)
			{
				ExitPosition(candle.ClosePrice, false);
				return;
			}

			if (profitPct <= -StopLoss * 0.1m)
			{
				ExitPosition(candle.ClosePrice, true);
				return;
			}

			if (_trailingLevel != 0m)
			{
				if ((_isLong && candle.ClosePrice <= _trailingLevel) ||
				(!_isLong && candle.ClosePrice >= _trailingLevel))
				{
					ExitPosition(candle.ClosePrice, candle.ClosePrice < _entryPrice);
				}
			}

			return;
		}
		
		// No open position -> decide direction
		var flip = _random.Next(0, 2);
		_isLong = flip == 0;
		
		_currentVolume = _lastTradeLoss
		? Math.Min(_currentVolume * Martingale, MaxVolume)
		: Volume;
		
		_entryPrice = candle.ClosePrice;
		_trailingLevel = 0m;
		
		if (_isLong)
		BuyMarket();
		else
		SellMarket();
	}
	
	private void ExitPosition(decimal closePrice, bool isLoss)
	{
		if (_isLong)
		{
			SellMarket();
		}
		else
		{
			BuyMarket();
		}
		
		_lastTradeLoss = isLoss;
		_entryPrice = 0m;
		_trailingLevel = 0m;
	}
}