在 GitHub 上查看

止损止盈策略

本策略为 MetaTrader “Stop Loss Take Profit” 专家的移植版本。每当账户没有持仓时,就抛掷一次“硬币”随机决定方向,并以市价单入场。成交后立即按照设定的点数(pip)放置止损和止盈订单。如果触发止损,下一笔交易的手数会加倍(同时受证券最大手数限制);若触发止盈,则手数恢复为初始值。整个过程沿用了原策略的马丁格尔风格,并基于 StockSharp 的高级 API 实现。

交易逻辑

  • 行情数据:通过参数 CandleType(默认 1 分钟 K 线)驱动信号。
  • 入场条件
    • Position == 0 且没有挂单时,生成一个伪随机布尔值。
    • true 使用 BuyMarket(volume) 做多,false 使用 SellMarket(volume) 做空。
  • 出场条件
    • 收到成交后立即提交止损和止盈委托。
    • 止损触发会将下一笔交易的手数翻倍,止盈触发则把手数重置为初始值。
    • 将止损或止盈距离设为 0 可以关闭相应的保护委托。
  • 仓位管理
    • InitialVolume 定义初始下单手数。
    • 亏损后手数翻倍,但会根据 Security.MaxVolume 自动截断,保证不超过交易所限制。
    • 手数会按照 VolumeStepMinVolumeMaxVolume 进行标准化,避免被交易所拒单。
  • Pip 处理
    • 默认根据证券的 PriceStepDecimals 推算 pip(例如五位报价的外汇会得到 0.0001)。
    • 参数 PipSize 大于 0 时,可手动指定 pip 的绝对价格大小。

参数

名称 默认值 说明
CandleType 1 分钟 K 线 触发随机信号所使用的时间周期。
StopLossPips 1 止损距离(以 pip 为单位),设为 0 表示不下止损单。
TakeProfitPips 1 止盈距离(以 pip 为单位),设为 0 表示不下止盈单。
InitialVolume 0.01 初始下单手数。止损后翻倍,止盈后恢复。
PipSize 0(自动) 可选的 pip 大小覆盖值,使用绝对价格单位。

使用提示

  • 同时支持做多和做空,方向完全由随机逻辑决定。
  • 头寸归零时会取消所有剩余的保护委托,避免遗留单子。
  • 随机数生成器使用 Environment.TickCount 作为种子,因此每次启动都会得到不同的交易序列。
  • 更适合作为风险管理与马丁格尔行为的演示案例,实盘使用前建议叠加额外的过滤器与风险控制。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Random direction strategy with pip-based stop loss and take profit that doubles the volume after losses.
/// </summary>
public class StopLossTakeProfitStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossDistance;
	private readonly StrategyParam<decimal> _takeProfitDistance;
	private readonly StrategyParam<decimal> _initialVolume;

	private decimal _currentVolume;
	private decimal _entryPrice;
	private int _tradeCount;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public decimal StopLossDistance
	{
		get => _stopLossDistance.Value;
		set => _stopLossDistance.Value = value;
	}

	public decimal TakeProfitDistance
	{
		get => _takeProfitDistance.Value;
		set => _takeProfitDistance.Value = value;
	}

	public decimal InitialVolume
	{
		get => _initialVolume.Value;
		set => _initialVolume.Value = value;
	}

	public StopLossTakeProfitStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for evaluating entries", "General");

		_stopLossDistance = Param(nameof(StopLossDistance), 5m)
			.SetDisplay("Stop Loss Distance", "Stop loss distance in price units", "Risk");

		_takeProfitDistance = Param(nameof(TakeProfitDistance), 5m)
			.SetDisplay("Take Profit Distance", "Take profit distance in price units", "Risk");

		_initialVolume = Param(nameof(InitialVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Initial Volume", "Starting order volume", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_currentVolume = 0;
		_entryPrice = 0;
		_tradeCount = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_currentVolume = InitialVolume;
		_entryPrice = 0m;

		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;

		// Check SL/TP for open position
		if (Position != 0 && _entryPrice > 0)
		{
			if (Position > 0)
			{
				var hitStop = StopLossDistance > 0 && candle.LowPrice <= _entryPrice - StopLossDistance;
				var hitTake = TakeProfitDistance > 0 && candle.HighPrice >= _entryPrice + TakeProfitDistance;

				if (hitStop)
				{
					SellMarket();
					HandleStopLoss();
					return;
				}

				if (hitTake)
				{
					SellMarket();
					HandleTakeProfit();
					return;
				}
			}
			else if (Position < 0)
			{
				var hitStop = StopLossDistance > 0 && candle.HighPrice >= _entryPrice + StopLossDistance;
				var hitTake = TakeProfitDistance > 0 && candle.LowPrice <= _entryPrice - TakeProfitDistance;

				if (hitStop)
				{
					BuyMarket();
					HandleStopLoss();
					return;
				}

				if (hitTake)
				{
					BuyMarket();
					HandleTakeProfit();
					return;
				}
			}
		}

		// Enter new position when flat
		if (Position == 0)
		{
			_tradeCount++;

			// Use candle direction as a deterministic signal
			if (candle.ClosePrice < candle.OpenPrice)
			{
				SellMarket();
			}
			else
			{
				BuyMarket();
			}

			_entryPrice = candle.ClosePrice;
		}
	}

	private void HandleStopLoss()
	{
		// Double volume on loss (martingale)
		_currentVolume *= 2m;

		var maxVol = Security?.MaxVolume;
		if (maxVol.HasValue && maxVol.Value > 0 && _currentVolume > maxVol.Value)
			_currentVolume = maxVol.Value;

		Volume = _currentVolume;
		_entryPrice = 0m;
	}

	private void HandleTakeProfit()
	{
		// Reset volume on profit
		_currentVolume = InitialVolume;
		Volume = _currentVolume;
		_entryPrice = 0m;
	}
}