GitHub で見る

Coin Flip Strategy

Overview

The Coin Flip Strategy randomly chooses to go long or short on each new candle when no position is open. After closing a position, if the trade ended in loss, the next trade size is increased using a martingale multiplier. The strategy closes positions using fixed take‑profit and stop‑loss levels defined in price steps and can optionally trail profits after a specified distance.

Parameters

  • Volume – base order size used for the first attempt.
  • Martingale – multiplier applied to the volume after a losing trade.
  • MaxVolume – upper limit for the position size after martingale increases.
  • TakeProfit – profit target in price steps.
  • StopLoss – loss limit in price steps.
  • TrailingStart – distance in price steps where trailing becomes active.
  • TrailingStop – trailing stop distance in price steps.
  • CandleType – time frame of candles used for decision making.

How It Works

  1. On each finished candle, the strategy checks for an open position.
  2. If a position exists, it monitors profit or loss using the current close price. Once take‑profit, stop‑loss, or trailing stop conditions are met, the position is closed.
  3. When no position is open, a virtual coin is flipped:
    • Heads opens a long position.
    • Tails opens a short position.
  4. If the previous trade was a loss, the volume is multiplied by Martingale but capped by MaxVolume.
  5. Trailing stop is engaged once price moves by TrailingStart in the favorable direction.

Notes

This example is intended for educational purposes to demonstrate how to work with random signals and position sizing using the StockSharp high‑level API.

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;
	}
}