在 GitHub 上查看

Roulette Game

Roulette Game 策略把 MetaTrader 上的同名游戏搬到了 StockSharp。每一根收盘的 K 线都被视为一次新的轮盘旋转,方向随机决定,若上一轮亏损则按照马丁格尔方式放大下一个仓位。策略维护一个虚拟资金池,并通过可配置的上限来抑制风险敞口。

每个回合都会先平掉当前持仓,然后抛“硬币”决定做多还是做空,并按该方向发送市价单。等到下一根 K 线收盘后,策略检查价格是否朝有利方向移动:若获胜,仓位系数回到初始值;若失败,则按倍率继续放大,直到到达设定的最大倍数或最大连亏次数。可以设置等待的 K 线数量,让回合之间留出冷却时间。

此次转换突出的是源策略中的博彩式资金管理思想,而非技术指标。它展示了如何围绕时间轮次组织逻辑、维护内部状态,并利用 StockSharp 的高级 API 进行烛线订阅和交易操作。

细节

  • 入场条件:没有技术过滤器,每次在完成的 K 线上随机选择方向。
  • 多空方向:双向,且每轮随机挑选。
  • 离场条件:下一根完成的 K 线收盘后平仓,并判断本轮胜负。
  • 止损:无固定止损,通过倍数上限和连亏限制来控制风险。
  • 默认参数
    • BaseVolume = 1m
    • LossMultiplier = 2m
    • MaxMultiplier = 16m
    • RoundCooldown = 1
    • MaxLosingStreak = 5
    • CandleType = TimeSpan.FromMinutes(1)
  • 筛选标签
    • 分类:资金管理
    • 方向:双向
    • 指标:无
    • 止损:无
    • 复杂度:入门
    • 周期:短周期
    • 季节性:否
    • 神经网络:否
    • 背离:否
    • 风险等级:高

备注

  • 订单数量依据当前倍率调整,并四舍五入到交易品种的量化步长。
  • 胜利后倍率重置,失败后倍率递增,直至达到上限或触发最大连亏约束。
  • 冷却期可以降低交易频率,并便于与较慢的数据源同步。
namespace StockSharp.Samples.Strategies;

using System;

using Ecng.Common;

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

/// <summary>
/// Roulette Game strategy: random-like entries based on candle direction with SMA filter.
/// Buys when candle is bullish and close above SMA. Sells when bearish and below SMA.
/// </summary>
public class RouletteGameStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaPeriod;

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

	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	public RouletteGameStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_smaPeriod = Param(nameof(SmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "SMA period", "Indicators");
	}

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

		var sma = new SimpleMovingAverage { Length = SmaPeriod };

		decimal? prevClose = null;
		decimal? prevSma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, (candle, smaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;
				var isBullish = close > candle.OpenPrice;

				if (prevClose.HasValue && prevSma.HasValue)
				{
					var crossUp = prevClose.Value <= prevSma.Value && close > smaVal;
					var crossDown = prevClose.Value >= prevSma.Value && close < smaVal;

					if (isBullish && crossUp && Position <= 0)
						BuyMarket();
					else if (!isBullish && crossDown && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevSma = smaVal;
			})
			.Start();

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