在 GitHub 上查看

抛硬币策略

概述

抛硬币策略 在没有持仓时,会在每根新蜡烛上随机选择做多或做空。若上一笔交易亏损,下一笔交易的手数会根据马丁系数增加。策略通过以价格步长表示的固定止盈和止损来平仓,并可在价格移动一定距离后启用追踪止损。

参数

  • Volume – 初始下单手数。
  • Martingale – 亏损后放大的马丁系数。
  • MaxVolume – 马丁放大后的最大手数限制。
  • TakeProfit – 以价格步长表示的止盈值。
  • StopLoss – 以价格步长表示的止损值。
  • TrailingStart – 激活追踪止损所需的价格步长。
  • TrailingStop – 追踪止损的价格步长。
  • CandleType – 用于决策的蜡烛周期。

工作流程

  1. 每根收盘蜡烛到来时,检查是否有持仓。
  2. 如果有持仓,根据收盘价计算盈亏;满足止盈、止损或追踪止损条件时平仓。
  3. 若没有持仓,则抛硬币决定方向:
    • 正面开多。
    • 反面开空。
  4. 若上一笔交易亏损,则按 Martingale 系数放大下次手数,但不超过 MaxVolume
  5. 当价格朝有利方向移动达到 TrailingStart 时启动追踪止损。

注意

该示例仅用于展示如何使用 StockSharp 高级 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;
	}
}