ピンボール マシンのランダム抽選戦略
概要
この戦略は、MetaTrader 4 エキスパート アドバイザー Pinball_machine.mq4 の直接の StockSharp 変換です。オリジナルロボットが描きました
受信ティックごとにランダムな整数を生成し、それらのドローのうち 2 つが一致するたびに成行注文を開きます。 StockSharp バージョン
同じ宝くじスタイルの動作を保持します。選択した時間枠の終了したローソク足ごとに、アルゴリズムは 2 つのペアを実行します。
対応するペアに等しい値が含まれている場合、ランダムにドローし、ロングまたはショートの市場ポジションを入力します。ストップロスとテイクプロフィット
距離も評価ごとにランダム化され、トレードが跳ね返って元の「ピンボール」ルーチンの感覚を再現します。
予想外にアウト。
取引ロジック
CandleTypeパラメータで定義されたローソク足をサブスクライブし、完全に形成されたバーを待ちます。- 完成したキャンドルごとに、
[0, RandomMaxValue]に均等に分散された 4 つの整数が生成されます。最初のペアは、 潜在的なロングエントリーの場合、2 番目のペアは潜在的なショートエントリーに属します。 MinStopLossPoints/MaxStopLossPointsとMinTakeProfitPoints/MaxTakeProfitPointsの間の 2 つの追加の整数を描画して、 評価の両側に共有される保護距離 (価格ステップで表される) を決定します。- 1 番目と 2 番目のランダムな整数が一致する場合は、数量
TradeVolumeの成行買い注文を送信します。 3番目と4番目の場合 値が一致する場合は、同じ数量で成行売り注文を送信します。どちらの条件も同じキャンドル内で点火できます。 MQL バージョンでは、買い注文と売り注文が独立したイベントでした。 - すぐにストップロス注文と利食い注文を追加します (描画距離がゼロより大きい場合)。距離が解釈される
金融商品の
PriceStepの倍数として、MetaTrader で使用されるPoint乗数をミラーリングします。
注文管理とリスク管理
StartProtection()は戦略の開始時に呼び出され、StockSharp が戦略に代わって保護命令を管理します。- 各エントリは結果の位置 (
Position ± TradeVolume) を測定し、それをSetStopLossとSetTakeProfitに渡します。 これにより、複数の取引が同時に実行されている場合でも、プラットフォームで保護注文を統合できます。 - 最小距離パラメータまたは最大距離パラメータのいずれかがゼロまたは負の数値に設定されている場合、対応する保護は次のようになります。 そのサイクルではスキップされました。
パラメーター
| パラメータ | 説明 |
|---|---|
TradeVolume |
ランダムなエントリごとに送信されたロット/契約の注文サイズ。 |
CandleType |
ランダムな抽選をトリガーするローソク足の時間枠。期間を短くすると、元のティックベースの EA がより厳密にエミュレートされます。 |
RandomMaxValue |
整数描画の包括的な上限。値が大きいほど、数字が一致する確率が低下するため、取引頻度が減少します。 |
MinStopLossPoints |
ランダムに生成されたストップロス距離の下限 (価格ステップ単位)。 |
MaxStopLossPoints |
ストップロス距離の上限(価格ステップ単位)。 |
MinTakeProfitPoints |
ランダムに生成されたテイクプロフィットディスタンスの下限(価格ステップ単位)。 |
MaxTakeProfitPoints |
利益確定距離の上限 (価格ステップ単位)。 |
RandomSeed |
擬似乱数生成器のシード。ゼロの場合は動作を時間ベースに保ち、その他の値を指定するとシーケンスが再現可能になります。 |
実装メモ
- MetaTrader スクリプトはティック駆動でした。高レベルの API は時系列イベントで動作するため、StockSharp ポートはローソク足の完了を使用します。非常に短い
CandleType(たとえば、1 秒またはティック ローソク足) を設定すると、元のペースの速い性質が復元されます。 - ストップロスとテイクプロフィットの値は、ソース EA とまったく同様に、評価ごとに 1 回生成され、ロングブランチとショートブランチの両方で再利用されます。
- 取引される商品が有効な
PriceStepを露出していることを確認してください。そうでない場合は、ポイントで表される保護距離を手動で調整する必要がある場合があります。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Lottery-style strategy that makes two independent random entry decisions per candle.
/// Each entry receives random stop-loss and take-profit distances measured in price steps.
/// </summary>
public class PinballMachineRandomDrawStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _randomMaxValue;
private readonly StrategyParam<int> _minStopLossPoints;
private readonly StrategyParam<int> _maxStopLossPoints;
private readonly StrategyParam<int> _minTakeProfitPoints;
private readonly StrategyParam<int> _maxTakeProfitPoints;
private readonly StrategyParam<int> _randomSeed;
private Random _random;
private decimal _protectedPosition;
private ProtectedPosition _protection;
public PinballMachineRandomDrawStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Volume submitted for every matching pair.", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candles that trigger random draws.", "General");
_randomMaxValue = Param(nameof(RandomMaxValue), 99)
.SetRange(0, 1_000_000)
.SetDisplay("Random Maximum", "Inclusive upper bound for entry draws.", "Random");
_minStopLossPoints = Param(nameof(MinStopLossPoints), 10)
.SetDisplay("Minimum Stop Loss", "Minimum random stop-loss distance in price steps.", "Protection");
_maxStopLossPoints = Param(nameof(MaxStopLossPoints), 50)
.SetDisplay("Maximum Stop Loss", "Maximum random stop-loss distance in price steps.", "Protection");
_minTakeProfitPoints = Param(nameof(MinTakeProfitPoints), 10)
.SetDisplay("Minimum Take Profit", "Minimum random take-profit distance in price steps.", "Protection");
_maxTakeProfitPoints = Param(nameof(MaxTakeProfitPoints), 100)
.SetDisplay("Maximum Take Profit", "Maximum random take-profit distance in price steps.", "Protection");
_randomSeed = Param(nameof(RandomSeed), 0)
.SetDisplay("Random Seed", "Zero uses a time-based sequence; any other value is reproducible.", "Random");
}
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RandomMaxValue
{
get => _randomMaxValue.Value;
set => _randomMaxValue.Value = value;
}
public int MinStopLossPoints
{
get => _minStopLossPoints.Value;
set => _minStopLossPoints.Value = value;
}
public int MaxStopLossPoints
{
get => _maxStopLossPoints.Value;
set => _maxStopLossPoints.Value = value;
}
public int MinTakeProfitPoints
{
get => _minTakeProfitPoints.Value;
set => _minTakeProfitPoints.Value = value;
}
public int MaxTakeProfitPoints
{
get => _maxTakeProfitPoints.Value;
set => _maxTakeProfitPoints.Value = value;
}
public int RandomSeed
{
get => _randomSeed.Value;
set => _randomSeed.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_random = null;
_protectedPosition = 0m;
_protection = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_random = RandomSeed == 0 ? new Random() : new Random(RandomSeed);
StartProtectionBook();
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;
ProcessProtection(candle);
var draw = DrawEvaluation(
_random,
RandomMaxValue,
MinStopLossPoints,
MaxStopLossPoints,
MinTakeProfitPoints,
MaxTakeProfitPoints);
if (draw.EnterLong)
SubmitEntry(Sides.Buy, candle, draw.StopLossPoints, draw.TakeProfitPoints);
if (draw.EnterShort)
SubmitEntry(Sides.Sell, candle, draw.StopLossPoints, draw.TakeProfitPoints);
}
private void SubmitEntry(Sides side, ICandleMessage candle, int stopLossPoints, int takeProfitPoints)
{
if (TradeVolume <= 0m)
return;
SubmitMarket(side, TradeVolume, "Pinball entry");
_protectedPosition += side == Sides.Buy ? TradeVolume : -TradeVolume;
if (_protectedPosition == 0m)
{
_protection = null;
return;
}
var priceStep = Security?.PriceStep;
if (priceStep is not decimal step || step <= 0m || (stopLossPoints <= 0 && takeProfitPoints <= 0))
{
_protection = null;
return;
}
var protection = new ProtectedPosition(
_protectedPosition > 0m ? Sides.Buy : Sides.Sell,
Math.Abs(_protectedPosition),
candle.CloseTime);
SetStopLoss(protection, candle.ClosePrice, stopLossPoints, step);
SetTakeProfit(protection, candle.ClosePrice, takeProfitPoints, step);
_protection = protection;
}
private void ProcessProtection(ICandleMessage candle)
{
var protection = _protection;
if (protection is null || candle.CloseTime <= protection.ActiveAfter)
return;
var stopHit = protection.StopLossPrice is decimal stop &&
(protection.Side == Sides.Buy ? candle.LowPrice <= stop : candle.HighPrice >= stop);
var takeHit = !stopHit && protection.TakeProfitPrice is decimal take &&
(protection.Side == Sides.Buy ? candle.HighPrice >= take : candle.LowPrice <= take);
if (!stopHit && !takeHit)
return;
var exitSide = protection.Side == Sides.Buy ? Sides.Sell : Sides.Buy;
SubmitMarket(exitSide, protection.Volume, "Pinball protection exit");
_protectedPosition = 0m;
_protection = null;
}
private void SubmitMarket(Sides side, decimal volume, string comment)
{
var order = CreateOrder(side, 0m, volume);
order.Comment = comment;
RegisterOrder(order);
}
private void StartProtectionBook()
{
_protectedPosition = Position;
_protection = null;
}
private static void SetStopLoss(ProtectedPosition protection, decimal entryPrice, int points, decimal priceStep)
{
if (points <= 0)
return;
var distance = points * priceStep;
protection.StopLossPrice = protection.Side == Sides.Buy
? entryPrice - distance
: entryPrice + distance;
}
private static void SetTakeProfit(ProtectedPosition protection, decimal entryPrice, int points, decimal priceStep)
{
if (points <= 0)
return;
var distance = points * priceStep;
protection.TakeProfitPrice = protection.Side == Sides.Buy
? entryPrice + distance
: entryPrice - distance;
}
internal static Evaluation DrawEvaluation(
Random random,
int randomMaxValue,
int minStopLossPoints,
int maxStopLossPoints,
int minTakeProfitPoints,
int maxTakeProfitPoints)
{
ArgumentNullException.ThrowIfNull(random);
if (randomMaxValue < 0)
throw new ArgumentOutOfRangeException(nameof(randomMaxValue));
int drawEntry() => randomMaxValue == int.MaxValue
? (int)random.NextInt64((long)int.MaxValue + 1L)
: random.Next(randomMaxValue + 1);
return new(
drawEntry(),
drawEntry(),
drawEntry(),
drawEntry(),
DrawDistance(random, minStopLossPoints, maxStopLossPoints),
DrawDistance(random, minTakeProfitPoints, maxTakeProfitPoints));
}
private static int DrawDistance(Random random, int minimum, int maximum)
{
if (minimum <= 0 || maximum <= 0 || minimum > maximum)
return 0;
return maximum == int.MaxValue
? (int)random.NextInt64(minimum, (long)maximum + 1L)
: random.Next(minimum, maximum + 1);
}
internal readonly record struct Evaluation(
int LongFirst,
int LongSecond,
int ShortFirst,
int ShortSecond,
int StopLossPoints,
int TakeProfitPoints)
{
public bool EnterLong => LongFirst == LongSecond;
public bool EnterShort => ShortFirst == ShortSecond;
}
private sealed class ProtectedPosition(Sides side, decimal volume, DateTime activeAfter)
{
public Sides Side { get; } = side;
public decimal Volume { get; } = volume;
public DateTime ActiveAfter { get; } = activeAfter;
public decimal? StopLossPrice { get; set; }
public decimal? TakeProfitPrice { get; set; }
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Random
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Strategies import Strategy
class pinball_machine_random_draw_strategy(Strategy):
def __init__(self):
super(pinball_machine_random_draw_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Trade Volume", "Volume submitted for every matching pair", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candles that trigger random draws", "General")
self._random_max_value = self.Param("RandomMaxValue", 99) \
.SetDisplay("Random Maximum", "Inclusive upper bound for entry draws", "Random")
self._min_stop_loss_points = self.Param("MinStopLossPoints", 10) \
.SetDisplay("Minimum Stop Loss", "Minimum random stop-loss distance in price steps", "Protection")
self._max_stop_loss_points = self.Param("MaxStopLossPoints", 50) \
.SetDisplay("Maximum Stop Loss", "Maximum random stop-loss distance in price steps", "Protection")
self._min_take_profit_points = self.Param("MinTakeProfitPoints", 10) \
.SetDisplay("Minimum Take Profit", "Minimum random take-profit distance in price steps", "Protection")
self._max_take_profit_points = self.Param("MaxTakeProfitPoints", 100) \
.SetDisplay("Maximum Take Profit", "Maximum random take-profit distance in price steps", "Protection")
self._random_seed = self.Param("RandomSeed", 0) \
.SetDisplay("Random Seed", "Zero uses a time-based sequence; any other value is reproducible", "Random")
self._random = None
self._protected_position = 0.0
self._protection = None
@property
def TradeVolume(self):
return self._trade_volume.Value
@TradeVolume.setter
def TradeVolume(self, value):
self._trade_volume.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RandomMaxValue(self):
return self._random_max_value.Value
@RandomMaxValue.setter
def RandomMaxValue(self, value):
self._random_max_value.Value = value
@property
def MinStopLossPoints(self):
return self._min_stop_loss_points.Value
@MinStopLossPoints.setter
def MinStopLossPoints(self, value):
self._min_stop_loss_points.Value = value
@property
def MaxStopLossPoints(self):
return self._max_stop_loss_points.Value
@MaxStopLossPoints.setter
def MaxStopLossPoints(self, value):
self._max_stop_loss_points.Value = value
@property
def MinTakeProfitPoints(self):
return self._min_take_profit_points.Value
@MinTakeProfitPoints.setter
def MinTakeProfitPoints(self, value):
self._min_take_profit_points.Value = value
@property
def MaxTakeProfitPoints(self):
return self._max_take_profit_points.Value
@MaxTakeProfitPoints.setter
def MaxTakeProfitPoints(self, value):
self._max_take_profit_points.Value = value
@property
def RandomSeed(self):
return self._random_seed.Value
@RandomSeed.setter
def RandomSeed(self, value):
self._random_seed.Value = value
def OnReseted(self):
super(pinball_machine_random_draw_strategy, self).OnReseted()
self._random = None
self._protected_position = 0.0
self._protection = None
def OnStarted2(self, time):
super(pinball_machine_random_draw_strategy, self).OnStarted2(time)
seed = int(self.RandomSeed)
self._random = Random() if seed == 0 else Random(seed)
self._start_protection_book()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._process_protection(candle)
draw = self._draw_evaluation()
if draw[0] == draw[1]:
self._submit_entry(Sides.Buy, candle, draw[4], draw[5])
if draw[2] == draw[3]:
self._submit_entry(Sides.Sell, candle, draw[4], draw[5])
def _draw_evaluation(self):
maximum = int(self.RandomMaxValue)
if maximum < 0:
raise ValueError("RandomMaxValue must not be negative")
values = [self._random.Next(maximum + 1) for _ in range(4)]
values.append(self._draw_distance(int(self.MinStopLossPoints), int(self.MaxStopLossPoints)))
values.append(self._draw_distance(int(self.MinTakeProfitPoints), int(self.MaxTakeProfitPoints)))
return values
def _draw_distance(self, minimum, maximum):
if minimum <= 0 or maximum <= 0 or minimum > maximum:
return 0
return self._random.Next(minimum, maximum + 1)
def _submit_entry(self, side, candle, stop_loss_points, take_profit_points):
volume = self.TradeVolume
if volume <= 0:
return
self._submit_market(side, volume, "Pinball entry")
self._protected_position += volume if side == Sides.Buy else -volume
if self._protected_position == 0:
self._protection = None
return
price_step = self.Security.PriceStep if self.Security is not None else None
if price_step is None or price_step <= 0 or (stop_loss_points <= 0 and take_profit_points <= 0):
self._protection = None
return
protection = {
"side": Sides.Buy if self._protected_position > 0 else Sides.Sell,
"volume": abs(self._protected_position),
"active_after": candle.CloseTime,
"stop": None,
"take": None,
}
self._set_stop_loss(protection, candle.ClosePrice, stop_loss_points, price_step)
self._set_take_profit(protection, candle.ClosePrice, take_profit_points, price_step)
self._protection = protection
def _process_protection(self, candle):
protection = self._protection
if protection is None or candle.CloseTime <= protection["active_after"]:
return
is_long = protection["side"] == Sides.Buy
stop = protection["stop"]
take = protection["take"]
stop_hit = stop is not None and (candle.LowPrice <= stop if is_long else candle.HighPrice >= stop)
take_hit = not stop_hit and take is not None and (candle.HighPrice >= take if is_long else candle.LowPrice <= take)
if not stop_hit and not take_hit:
return
exit_side = Sides.Sell if is_long else Sides.Buy
self._submit_market(exit_side, protection["volume"], "Pinball protection exit")
self._protected_position = 0.0
self._protection = None
def _submit_market(self, side, volume, comment):
order = self.CreateOrder(side, 0, volume)
order.Comment = comment
self.RegisterOrder(order)
def _start_protection_book(self):
self._protected_position = self.Position
self._protection = None
@staticmethod
def _set_stop_loss(protection, entry_price, points, price_step):
if points <= 0:
return
distance = points * price_step
protection["stop"] = entry_price - distance if protection["side"] == Sides.Buy else entry_price + distance
@staticmethod
def _set_take_profit(protection, entry_price, points, price_step):
if points <= 0:
return
distance = points * price_step
protection["take"] = entry_price + distance if protection["side"] == Sides.Buy else entry_price - distance
def CreateClone(self):
return pinball_machine_random_draw_strategy()