Monte Carlo Simulation - Random Walk Strategy
This sample strategy performs a Monte Carlo simulation of future price paths using historical log returns. It does not place trades but demonstrates how to generate random walks and estimate future max and min price levels.
Details
- Entry Criteria: none, this strategy does not trade.
- Long/Short: none.
- Exit Criteria: not applicable.
- Stops: none.
- Default Values:
NumberOfBarsToPredict= 50.NumberOfSimulations= 500.DataLength= 2000.KeepPastMinMaxLevels= false.
- Filters: not applicable.
- Complexity: medium.
- Timeframe: configurable.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Monte Carlo simulation random walk strategy.
/// Uses MC simulation to estimate expected price range and trades based on mean forecast.
/// </summary>
public class MonteCarloSimulationRandomWalkStrategy : Strategy
{
private readonly StrategyParam<int> _forecastBars;
private readonly StrategyParam<int> _simulations;
private readonly StrategyParam<int> _dataLength;
private readonly StrategyParam<decimal> _minForecastEdgePercent;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _returns = new();
private decimal? _prevClose;
private int _barsFromSignal;
public int ForecastBars { get => _forecastBars.Value; set => _forecastBars.Value = value; }
public int Simulations { get => _simulations.Value; set => _simulations.Value = value; }
public int DataLength { get => _dataLength.Value; set => _dataLength.Value = value; }
public decimal MinForecastEdgePercent { get => _minForecastEdgePercent.Value; set => _minForecastEdgePercent.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MonteCarloSimulationRandomWalkStrategy()
{
_forecastBars = Param(nameof(ForecastBars), 10).SetGreaterThanZero();
_simulations = Param(nameof(Simulations), 100).SetGreaterThanZero();
_dataLength = Param(nameof(DataLength), 100).SetGreaterThanZero();
_minForecastEdgePercent = Param(nameof(MinForecastEdgePercent), 0.5m).SetGreaterThanZero();
_signalCooldownBars = Param(nameof(SignalCooldownBars), 12).SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_returns.Clear();
_prevClose = null;
_barsFromSignal = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_returns.Clear();
_prevClose = null;
_barsFromSignal = SignalCooldownBars;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevClose is decimal prevClose && prevClose > 0)
{
var ret = (decimal)Math.Log((double)(candle.ClosePrice / prevClose));
_returns.Add(ret);
if (_returns.Count > DataLength)
_returns.RemoveAt(0);
}
_prevClose = candle.ClosePrice;
if (_returns.Count < 20)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
_barsFromSignal++;
var history = _returns.ToArray();
var avg = history.Average();
var variance = history.Select(r => (r - avg) * (r - avg)).Average();
var drift = avg - variance / 2m;
var random = new Random(unchecked((int)candle.OpenTime.Ticks));
double sum = 0;
for (var sim = 0; sim < Simulations; sim++)
{
var price = (double)candle.ClosePrice;
for (var step = 0; step < ForecastBars; step++)
{
var idx = random.Next(history.Length);
price *= Math.Exp((double)(history[idx] + drift));
}
sum += price;
}
var meanForecast = (decimal)(sum / Simulations);
var current = candle.ClosePrice;
var edgePercent = (meanForecast - current) / current * 100m;
if (_barsFromSignal >= SignalCooldownBars && edgePercent >= MinForecastEdgePercent && Position <= 0)
{
BuyMarket();
_barsFromSignal = 0;
}
else if (_barsFromSignal >= SignalCooldownBars && edgePercent <= -MinForecastEdgePercent && Position >= 0)
{
SellMarket();
_barsFromSignal = 0;
}
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class monte_carlo_simulation_random_walk_strategy(Strategy):
def __init__(self):
super(monte_carlo_simulation_random_walk_strategy, self).__init__()
self._forecast_bars = self.Param("ForecastBars", 10) \
.SetGreaterThanZero()
self._simulations = self.Param("Simulations", 100) \
.SetGreaterThanZero()
self._data_length = self.Param("DataLength", 100) \
.SetGreaterThanZero()
self._min_forecast_edge_percent = self.Param("MinForecastEdgePercent", 0.5) \
.SetGreaterThanZero()
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 12) \
.SetGreaterThanZero()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._returns = []
self._prev_close = 0.0
self._bars_from_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(monte_carlo_simulation_random_walk_strategy, self).OnReseted()
self._returns = []
self._prev_close = 0.0
self._bars_from_signal = 0
def OnStarted2(self, time):
super(monte_carlo_simulation_random_walk_strategy, self).OnStarted2(time)
self._returns = []
self._prev_close = 0.0
self._bars_from_signal = self._signal_cooldown_bars.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
if self._prev_close > 0.0:
ret = math.log(close / self._prev_close)
self._returns.append(ret)
dl = self._data_length.Value
if len(self._returns) > dl:
self._returns.pop(0)
self._prev_close = close
if len(self._returns) < 20:
return
self._bars_from_signal += 1
history = self._returns[:]
n = len(history)
avg = sum(history) / n
variance = sum((r - avg) ** 2 for r in history) / n
drift = avg - variance / 2.0
seed = int(candle.OpenTime.Ticks) & 0x7FFFFFFF
import random as rnd_mod
rng = rnd_mod.Random(seed)
total = 0.0
sims = self._simulations.Value
fb = self._forecast_bars.Value
for sim in range(sims):
price = close
for step in range(fb):
idx = rng.randint(0, n - 1)
price *= math.exp(history[idx] + drift)
total += price
mean_forecast = total / sims
edge_percent = (mean_forecast - close) / close * 100.0
min_edge = float(self._min_forecast_edge_percent.Value)
cd = self._signal_cooldown_bars.Value
if self._bars_from_signal >= cd and edge_percent >= min_edge and self.Position <= 0:
self.BuyMarket()
self._bars_from_signal = 0
elif self._bars_from_signal >= cd and edge_percent <= -min_edge and self.Position >= 0:
self.SellMarket()
self._bars_from_signal = 0
def CreateClone(self):
return monte_carlo_simulation_random_walk_strategy()