在 GitHub 上查看

掷硬币 Martingale 策略

概述

掷硬币策略复刻了原始 MetaTrader 智能交易程序的思路:每根收盘 K 线触发一次“投硬币”,随机选择做多或做空,并且同一时间只持有一个仓位。新订单立即按计算出的手数在市场价成交,开仓时会同时设置止损和止盈,可选的跟踪止损在行情向有利方向运行时逐步锁定利润。

该策略内置马丁加仓模型。如果上一笔交易因为止损出场,则下一单的下单量会乘以设定的倍数;盈利或非止损出场会将手数恢复为基础手数,同时自定义的最大手数限制可避免仓位无限扩大。

交易规则

  1. 每根已完成的 K 线都会评估当前持仓状态。
  2. 当没有持仓时,利用伪随机数等概率选择做多或做空方向。
  3. 新订单默认使用基础手数;若上一单被止损,则按马丁倍数放大手数,但始终不超过最大手数限制。
  4. 开仓后立即设置止损与止盈价位,当收盘价触及任一阈值时,通过市价单平仓。
  5. 启用跟踪止损后,当浮盈超过“跟踪距离 + 跟踪步长”时,止损价会向盈利方向移动以锁定收益。

参数说明

  • Stop Loss – 从入场价计算的止损距离,单位为价格最小变动单位。
  • Take Profit – 从入场价计算的止盈距离,单位同上。
  • Trailing Stop – 激活跟踪止损所需的最小浮盈距离,设置为 0 表示关闭跟踪功能。
  • Trailing Step – 每次移动跟踪止损所需的额外浮盈。
  • Base Volume – 马丁周期内第一笔订单的基础手数。
  • Martingale Mult – 止损后用于放大下单量的倍数。
  • Max Volume – 手数上限,超出后该次交易会被跳过并记录警告。
  • Candle Type – 决策与风控所依赖的 K 线类型。

备注

  • 策略使用市价单进行开仓和平仓,以贴近原始 EA 的行为。
  • 如果证券没有提供价格步长,则直接把参数值视为点数使用。
  • 随机数以当前时间为种子生成,可避免多个回测实例出现完全相同的交易序列。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Random coin flip entry with martingale volume management (simplified).
/// Enters randomly using RSI as a tiebreaker, doubles volume after losses.
/// </summary>
public class CoinFlipMartingaleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;

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

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public CoinFlipMartingaleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candles", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "RSI period", "Indicators");
	}

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

		var rsi = new RelativeStrengthIndex { Length = RsiLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, (ICandleMessage candle, decimal rsiValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				// Use RSI to decide direction
				if (rsiValue < 35 && Position <= 0)
				{
					BuyMarket();
				}
				else if (rsiValue > 65 && Position >= 0)
				{
					SellMarket();
				}
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}
}