using System; using System.Collections.Generic; using System.Threading; using TradingPlatform.BusinessLayer; using TradingPlatform.BusinessLayer.Integration; namespace SyntheticVendorNamespace { public class SyntheticVendor : Vendor { private readonly List exchanges; private readonly List assets; private readonly List symbols; public SyntheticVendor() { exchanges = new List { //Spike, //Impulse, //Triangle, //Sawtooth //Sine //Chirp //White //Gauss //B //HF //Impulse+HF, //Sawtooth+HF //Sine+G //Chirp+G //Complex //Market new MessageExchange { Id = "PU", ExchangeName = "1 Pulse" }, new MessageExchange { Id = "WA", ExchangeName = "2 Wave" }, new MessageExchange { Id = "MD", ExchangeName = "3 Modulation" }, new MessageExchange { Id = "NO", ExchangeName = "4 Noise" }, new MessageExchange { Id = "BR", ExchangeName = "5 Brownian" }, new MessageExchange { Id = "QT", ExchangeName = "6 QuanTAlib" } }; assets = new List { new MessageAsset { Id = "USD", Name = "USD" }, }; symbols = new List { CreateMessageSymbol(id: "W1", name: "1 Digital spike", exchangeId: "QT", assetId: "USD", type: SymbolType.Crypto, description: "Sudden sharp spike in the signal"), CreateMessageSymbol("W2", "2 Dirac delta spike", "QT", "USD", SymbolType.Crypto), CreateMessageSymbol("W8", "4 Sinc pulse", "QT", "USD", SymbolType.Crypto), CreateMessageSymbol("W3", "1 Square Wave", "QT", "USD", SymbolType.ETF), CreateMessageSymbol("W4", "2 Sawtooth Wave", "QT", "USD", SymbolType.ETF), CreateMessageSymbol("W5", "3 Inverse sawtooth Wave", "QT", "USD", SymbolType.ETF), CreateMessageSymbol("W6", "4 Triangle Wave", "QT", "USD", SymbolType.ETF), CreateMessageSymbol("W7", "5 Sine Wave", "QT", "USD", SymbolType.ETF), CreateMessageSymbol("W11", "1 Amplitude modulation", "QT", "USD", SymbolType.Forex), CreateMessageSymbol("W10", "2 Frequency sweep", "QT", "USD", SymbolType.Forex), CreateMessageSymbol("W12", "3 Frequency modulation", "QT", "USD", SymbolType.Forex), CreateMessageSymbol("W13", "1 White noise", "QT", "USD", SymbolType.Indexes), CreateMessageSymbol("W14", "2 Pink noise", "QT", "USD", SymbolType.Indexes), CreateMessageSymbol("W15", "3 Brown noise", "QT", "USD", SymbolType.Indexes), CreateMessageSymbol("W16", "1 Fractional Brownian motion", "QT", "USD", SymbolType.Synthetic), CreateMessageSymbol("W17", "2 Geometric Brownian motion", "QT", "USD", SymbolType.Synthetic) }; /* Bond, CFD, Crypto, Debentures, Equities, ETF, FixedIncome, Forex, Forward, Futures, Indexes, Options, Spot, Synthetic, Swap, Warrants, */ } private MessageSymbol CreateMessageSymbol( string id, string name, string exchangeId, string assetId, SymbolType type, string description) { var messageSymbol = new MessageSymbol(id) { Name = name, Description = description, SymbolType = type, ExchangeId = exchangeId, ProductAssetId = assetId, // Setting some default values QuotingCurrencyAssetID = "USD", HistoryType = HistoryType.Last, DeltaCalculationType = DeltaCalculationType.TickDirection, LotSize = 1, VariableTickList = new List { new VariableTick(0.01) // Default tick size } }; return messageSymbol; } public static VendorMetaData GetVendorMetaData() { return new VendorMetaData() { VendorName = "Synthetic Vendor", VendorDescription = "A synthetic vendor for testing and demonstration purposes", GetDefaultConnections = () => { var defaultConnection = Vendor.CreateDefaultConnectionInfo( "Synthetic Connection", "Synthetic Vendor", "", // Replace with actual path if you have a logo allowCreateCustomConnections: true ); return new List { defaultConnection }; } }; } private MessageSymbol CreateMessageSymbol(string id, string name, string exchangeId, string assetId, SymbolType type) { return new MessageSymbol(id) { Name = name, ExchangeId = exchangeId, ProductAssetId = assetId, QuotingCurrencyAssetID = "USD", QuotingType = SymbolQuotingType.LotSize, LotSize = 1, NettingType = NettingType.OnePosition, VolumeType = SymbolVolumeType.Volume, AllowCalculateRealtimeTicks = true, AllowCalculateRealtimeTrades = false, AllowCalculateRealtimeVolume = true, AllowCalculateRealtimeChange = true, AllowAbbreviatePriceByTickSize = false, NotionalValueStep = 0.01, DeltaCalculationType = DeltaCalculationType.AggressorFlag, // Changed from None to AggressorFlag MinVolumeAnalysisTickSize = 0.01, MaturityDate = DateTime.MaxValue, // Set to max value for non-expiring symbols HistoryType = HistoryType.Last, MinLot = 0.01, LotStep = 0.01, MaxLot = 1000000, SymbolType = type /* SymbolType.Unknown, [EnumMember] Forex, [EnumMember] Equities, [EnumMember] CFD, [EnumMember] Indexes, [EnumMember] Futures, [EnumMember] Options, [EnumMember] ETF, [EnumMember] Crypto, [EnumMember] Synthetic, [EnumMember] Spot, [EnumMember] Forward, [EnumMember] FixedIncome, [EnumMember] Warrants, [EnumMember] Debentures, [EnumMember] Bond, [EnumMember] Swap, */ }; } public override ConnectionResult Connect(ConnectRequestParameters connectRequestParameters) { // Simulating connection process Thread.Sleep(100); // Simulate some connection delay return ConnectionResult.CreateSuccess("Successfully connected to Synthetic Vendor"); } public override void Disconnect() { // Simulating disconnection process Thread.Sleep(500); // Simulate some disconnection delay } public override PingResult Ping() { return new PingResult() { State = PingEnum.Connected, PingTime = TimeSpan.FromMilliseconds(2), RoundTripTime = TimeSpan.FromMilliseconds(2) }; } public override void OnConnected(CancellationToken token) { // This method is called after a successful connection // You can initialize resources or start any necessary processes here base.OnConnected(token); // For example, you might want to push some initial messages or data // PushMessage(new MessageVendorEvent("SyntheticVendor connected successfully")); } public override IList GetExchanges(CancellationToken token) { return exchanges; } public override IList GetAssets(CancellationToken token) { return assets; } public override IList GetSymbols(CancellationToken token) { return symbols; } public override void SubscribeSymbol(SubscribeQuotesParameters parameters) { // Empty method for data subscription to be filled later } public override void UnSubscribeSymbol(SubscribeQuotesParameters parameters) { // Empty method for data unsubscription to be filled later } public override IList LoadHistory(HistoryRequestParameters requestParameters) { var historyItems = new List(); var symbolId = requestParameters.SymbolId; if (string.IsNullOrEmpty(symbolId)) return historyItems; DateTime from = requestParameters.FromTime; DateTime to = requestParameters.ToTime; TimeSpan periodTimeSpan = requestParameters.Aggregation.GetPeriod.Duration; // Define the maximum number of items to generate per request const int MAX_ITEMS_PER_REQUEST = 10000; Func waveGenerator = GetWaveGenerator(symbolId); DateTime currentTime = from; while (currentTime < to) { DateTime intervalEnd = currentTime.AddTicks(periodTimeSpan.Ticks * MAX_ITEMS_PER_REQUEST); if (intervalEnd > to) intervalEnd = to; while (currentTime <= intervalEnd) { var historyItem = waveGenerator(currentTime, periodTimeSpan); //calling generator fuction historyItems.Add(historyItem); currentTime = currentTime.Add(periodTimeSpan); if (requestParameters.CancellationToken.IsCancellationRequested) return historyItems; } currentTime = intervalEnd; } return historyItems; } private Func GetWaveGenerator(string symbolId) { switch (symbolId) { //case "W0": return GenerateConstant; case "W1": return GenerateSpike; case "W2": return GenerateDiracDelta; case "W3": return GenerateSquareWave; case "W4": return GenerateSawtoothWave; case "W5": return GenerateInverseSawtoothWave; case "W6": return GenerateTriangleWave; case "W7": return GenerateSineWave; case "W8": return GenerateSincWave; case "W9": return GenerateGaussianPulse; case "W10": return GenerateFrequencySweep; case "W11": return GenerateAMSignal; case "W12": return GenerateFMSignal; case "W13": return GenerateWhiteNoise; case "W14": return GeneratePinkNoise; case "W15": return GenerateBrownNoise; case "W16": return GenerateFBM; case "W17": return GenerateGBM; default: return GenerateSineWave; } } public override HistoryMetadata GetHistoryMetadata(CancellationToken cancellationToken) { return new HistoryMetadata() { AllowedHistoryTypes = new HistoryType[] { HistoryType.Bid, HistoryType.Ask, HistoryType.Midpoint, HistoryType.Last, HistoryType.BidAsk, HistoryType.Mark, }, AllowedPeriods = new Period[] { Period.TICK1, Period.SECOND1, Period.SECOND5, Period.SECOND10, Period.SECOND15, Period.SECOND30, Period.MIN1, Period.MIN2, Period.MIN3, Period.MIN4, Period.MIN5, Period.MIN10, Period.MIN15, Period.MIN30, Period.HOUR1, Period.HOUR2, Period.HOUR3, Period.HOUR4, Period.HOUR6, Period.HOUR8, Period.HOUR12, Period.DAY1, Period.WEEK1, Period.MONTH1, Period.YEAR1 }, UseHistoryLocalCache = false }; } /*******************************************************************************************************************************************/ /*******************************************************************************************************************************************/ /*******************************************************************************************************************************************/ /*******************************************************************************************************************************************/ /*******************************************************************************************************************************************/ /*******************************************************************************************************************************************/ /*******************************************************************************************************************************************/ private HistoryItemBar GenerateSpike(DateTime time, TimeSpan slice) { // Ensure we're working with UTC time DateTime utcTime = time.ToUniversalTime(); // Calculate the number of hours since the epoch double hoursSinceEpoch = (utcTime - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalHours; // Calculate the position within the 25-hour cycle int cyclePosition = (int)Math.Floor(hoursSinceEpoch % 25); // Determine if this is a spike hour (hour 24 in the cycle) or the hour after bool isSpike = cyclePosition == 24; bool isAfterSpike = cyclePosition == 0; double openValue, closeValue; if (isSpike) { openValue = 0; closeValue = 100; } else if (isAfterSpike) { openValue = 100; closeValue = 0; } else { openValue = closeValue = 0.000001; } return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = openValue, High = Math.Max(openValue, closeValue), Low = Math.Min(openValue, closeValue), Close = closeValue, Volume = Math.Abs(closeValue - openValue), Ticks = time.Add(slice).Ticks - time.Ticks }; } private static readonly double[] distributionValues = new double[] { 0.010, // Extreme left tail 0.050, // Left tail 0.200, // Left of center 0.480, // Center (peak) 0.200, // Right of center 0.050, // Right tail 0.010 // Extreme right tail }; private HistoryItemBar GenerateDiracDelta(DateTime time, TimeSpan slice) { // Ensure we're working with UTC time DateTime utcTime = time.ToUniversalTime(); // Calculate the start of the current day DateTime dayStart = utcTime.Date; // Determine which bar of the day we're on int barOfDay = (int)((utcTime - dayStart).Ticks / slice.Ticks); double openValue, closeValue; double scaleFactor = 100; // Scale factor to convert to percentage // Generate the spike pattern for the first 4 bars of each day switch (barOfDay) { case 0: openValue = 0.000001 * scaleFactor; closeValue = 0.05 * scaleFactor; break; case 1: openValue = 0.05 * scaleFactor; closeValue = 0.50 * scaleFactor; break; case 2: openValue = 0.50 * scaleFactor; closeValue = 0.05 * scaleFactor; break; case 3: openValue = 0.05 * scaleFactor; closeValue = 0.0000001 * scaleFactor; break; default: // Outside of the spike period, use baseline value openValue = closeValue = 0.000001; break; } return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = openValue, High = Math.Max(openValue, closeValue), Low = Math.Min(openValue, closeValue), Close = closeValue, Volume = Math.Abs(closeValue - openValue), Ticks = time.Add(slice).Ticks - time.Ticks }; } private HistoryItemBar GenerateSineWave(DateTime time, TimeSpan slice) { // Ensure we're working with UTC time DateTime utcTime = time.ToUniversalTime(); // Calculate the number of hours since the epoch double minutesSinceEpoch = (utcTime - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMinutes; // Calculate the position within the 25-hour cycle double cyclePosition = minutesSinceEpoch % 1500; // Calculate the sine wave values double frequency = 2 * Math.PI / 1500; // Complete cycle over 25 hours double value = 50 + 50 * Math.Sin(cyclePosition * frequency); // Oscillate between 0 and 100 double nextValue = 50 + 50 * Math.Sin((cyclePosition + slice.TotalMinutes) * frequency); double factor = 0.6 * Math.Abs (nextValue - value); return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = value, High = Math.Max(value, nextValue)+factor, Low = Math.Min(value, nextValue)-factor, Close = nextValue, Volume = Math.Abs(nextValue - value) * 100, // Volume proportional to price change Ticks = time.Add(slice).Ticks - time.Ticks }; } private HistoryItemBar GenerateSquareWave(DateTime time, TimeSpan slice) { // Ensure we're working with UTC time DateTime utcTime = time.ToUniversalTime(); // Calculate the time within the day (in hours) double hoursInDay = utcTime.TimeOfDay.TotalHours; double openValue, closeValue; if (hoursInDay < 12) { // First half of the day openValue = 99; closeValue = 100; } else { // Second half of the day openValue = 1; closeValue = 0.0001; } // Handle transition bars if (Math.Abs(hoursInDay - 12) < slice.TotalHours / 2) { // Transition from 100 to 0 at noon openValue = 100; closeValue = 0.0001; } else if (hoursInDay < slice.TotalHours / 2 || hoursInDay > 24 - slice.TotalHours / 2) { // Transition from 0 to 100 at midnight openValue = 0.0001; closeValue = 100; } else { // No action } return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = openValue, High = Math.Max(openValue, closeValue), Low = Math.Min(openValue, closeValue), Close = closeValue, Volume = Math.Abs(closeValue - openValue), Ticks = time.Add(slice).Ticks - time.Ticks }; } private HistoryItemBar GenerateSawtoothWave(DateTime time, TimeSpan slice) { double hours = (time - DateTime.UnixEpoch).TotalHours; double period = 24; // 24-hour period double position = hours % period; double value = 200 * (position / period) - 100; double nextValue = 200 * ((position + slice.TotalHours) % period / period) - 100; return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = value, High = Math.Max(value, nextValue), Low = Math.Min(value, nextValue), Close = nextValue, Volume = 100, Ticks = 100 }; } private HistoryItemBar GenerateInverseSawtoothWave(DateTime time, TimeSpan slice) { double hours = (time - DateTime.UnixEpoch).TotalHours; double period = 24; // 24-hour period double position = hours % period; double value = 100 - (200 * (position / period)); double nextValue = 100 - (200 * ((position + slice.TotalHours) % period / period)); return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = value, High = Math.Max(value, nextValue), Low = Math.Min(value, nextValue), Close = nextValue, Volume = 100, Ticks = 100 }; } private HistoryItemBar GeneratePulseWave(DateTime time, TimeSpan slice) { double hours = (time - DateTime.UnixEpoch).TotalHours; double period = 24; // 24-hour period double position = hours % period; double value = position < period / 5 ? 100 : -100; // 20% duty cycle return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = value, High = 100, Low = -100, Close = value, Volume = 100, Ticks = 100 }; } private HistoryItemBar GenerateTriangleWave(DateTime time, TimeSpan slice) { double hours = (time - DateTime.UnixEpoch).TotalHours; double period = 24; double position = hours % period; double value = 200 * (Math.Abs(position / period - 0.5) - 0.25) * 100; double nextValue = 200 * (Math.Abs(((position + slice.TotalHours) % period) / period - 0.5) - 0.25) * 100; return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = value, High = Math.Max(value, nextValue), Low = Math.Min(value, nextValue), Close = nextValue, Volume = 100, Ticks = 100 }; } private HistoryItemBar GenerateSincWave(DateTime time, TimeSpan slice) { double minutes = (time - DateTime.UnixEpoch).TotalMinutes; double period = 1500.0; // 24-hour period double frequency = 2 * Math.PI / period; // Full cycle over 24 hours // Adjust time to center the main peak at 12 hours double t = minutes % period - period / 2; // Scale factor double scaleFactor = 7.0; // Calculate Sinc value double x = scaleFactor * frequency * t; double sincValue = x != 0 ? 100 * Math.Sin(x) / x : 100; // Calculate next value double nextT = ((minutes + slice.TotalMinutes) % period) - period / 2; double nextX = scaleFactor * frequency * nextT; double nextSincValue = nextX != 0 ? 100 * Math.Sin(nextX) / nextX : 100; // Ensure minimum value double minValue = 0.00001; sincValue = Math.Sign(sincValue) * Math.Max(Math.Abs(sincValue), minValue); nextSincValue = Math.Sign(nextSincValue) * Math.Max(Math.Abs(nextSincValue), minValue); return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = sincValue, High = Math.Max(sincValue, nextSincValue), Low = Math.Min(sincValue, nextSincValue), Close = nextSincValue, Volume = Math.Abs(nextSincValue - sincValue), // Volume as the change in value Ticks = slice.Ticks }; } private HistoryItemBar GenerateGaussianPulse(DateTime time, TimeSpan slice) { double hours = (time - DateTime.UnixEpoch).TotalHours; double totalPeriod = 24.0; // 24-hour total cycle double pulsePeriod = 12.0; // 12-hour pulse duration double position = hours % totalPeriod; // Parameters for the Gaussian pulse double amplitude = 100.0; // Maximum amplitude double center = pulsePeriod / 2.0; // Center of the pulse (at 6 hours within the pulse period) double width = pulsePeriod / 6.0; // Width of the pulse (adjusts the spread) double baselineValue = 0.00001; // Value outside the pulse period // Calculate the Gaussian pulse value double value; if (position < pulsePeriod) { value = amplitude * Math.Exp(-Math.Pow(position - center, 2) / (2 * Math.Pow(width, 2))) + baselineValue; } else { value = baselineValue; } // Calculate the next value for the slice double nextPosition = (hours + slice.TotalHours) % totalPeriod; double nextValue; if (nextPosition < pulsePeriod) { nextValue = amplitude * Math.Exp(-Math.Pow(nextPosition - center, 2) / (2 * Math.Pow(width, 2))) + baselineValue; } else { nextValue = baselineValue; } return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = value, High = Math.Max(value, nextValue), Low = Math.Min(value, nextValue), Close = nextValue, Volume = Math.Abs(nextValue - value), // Volume as the change in value Ticks = slice.Ticks }; } private HistoryItemBar GenerateFrequencySweep(DateTime time, TimeSpan slice) { double hours = (time - DateTime.UnixEpoch).TotalHours; double sweepPeriod = 48.0; // 48-hour period // Starting frequency (very low) double minFreq = Math.PI / 48.0; // Calculate the ending frequency to ensure continuity double maxFreq = Math.PI * 1.0 * Math.Exp(2 * Math.PI / sweepPeriod); // Calculate the exponential factor for frequency sweep double expFactor = Math.Log(maxFreq / minFreq) / sweepPeriod; // Calculate the overall phase up to the current time double totalPhase = (minFreq / expFactor) * (Math.Exp(expFactor * (hours % sweepPeriod)) - 1); // Shift the phase to start the cycle at 100 (cosine-like behavior) totalPhase += Math.PI / 2; // Calculate the value of the signal at the current time double value = 100.0 * Math.Sin(totalPhase); // Calculate the value of the signal at the end of the slice double nextPhase = (minFreq / expFactor) * (Math.Exp(expFactor * ((hours + slice.TotalHours) % sweepPeriod)) - 1); nextPhase += Math.PI / 2; // Apply the same phase shift double nextValue = 100.0 * Math.Sin(nextPhase); return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = value, High = Math.Max(value, nextValue), Low = Math.Min(value, nextValue), Close = nextValue, Volume = Math.Abs(nextValue - value), // Volume as the change in value Ticks = slice.Ticks }; } #pragma warning disable S2245 // NOSONAR readonly Random random = new Random(); #pragma warning restore S2245 private double currentAmplitude = 100; private HistoryItemBar GenerateAMSignal(DateTime time, TimeSpan slice) { double hours = (time - DateTime.UnixEpoch).TotalHours; double period = 12.0; double frequency = 2 * Math.PI / period; // Frequency for a 5-hour period // Determine the start of the current 5-hour cycle double cycleStartTime = Math.Floor(hours / period) * period; // Calculate the phase of the signal within the current 5-hour cycle double phase = frequency * (hours % period); // If we're at the start of a new 5-hour cycle, generate a new amplitude if (hours % period == 0) { currentAmplitude = random.NextDouble() * 100; } // Calculate the value of the signal at the current time double value = currentAmplitude * Math.Sin(phase); // Calculate the value of the signal at the end of the slice double nextPhase = frequency * ((hours + slice.TotalHours) % period); double nextValue = currentAmplitude * Math.Sin(nextPhase); // Create the HistoryItemBar var historyItem = new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = value, High = Math.Max(value, nextValue), Low = Math.Min(value, nextValue), Close = nextValue, // Set Close to the newly calculated value Volume = Math.Abs(nextValue), // Volume as the change in value Ticks = slice.Ticks }; return historyItem; } private double currentFrequency = Math.PI / 220.0; // Initial frequency private double accumulatedPhase = 0; private double lastCloseValue = 0; // To store the last close value private HistoryItemBar GenerateFMSignal(DateTime time, TimeSpan slice) { double amplitude = 100.0; // Maximum amplitude double minFreq = Math.PI / 256.0; double maxFreq = Math.PI / 32.0; // Randomly adjust the frequency double frequencyStep = (maxFreq - minFreq) * 0.2; // 20% of the frequency range currentFrequency += (random.NextDouble() - 0.5) * 2 * frequencyStep; currentFrequency = Math.Max(minFreq, Math.Min(maxFreq, currentFrequency)); // Clamp frequency // Calculate phase increment for this slice double phaseIncrement = currentFrequency * slice.TotalHours; // Calculate the open value (which is the last close value) double openValue = lastCloseValue; // Calculate the close value accumulatedPhase += phaseIncrement; double closeValue = amplitude * Math.Sin(2 * Math.PI * accumulatedPhase); // Determine high and low values double midPhase = accumulatedPhase - (phaseIncrement / 2); double midValue = amplitude * Math.Sin(2 * Math.PI * midPhase); double highValue = Math.Max(Math.Max(openValue, closeValue), midValue); double lowValue = Math.Min(Math.Min(openValue, closeValue), midValue); // Store the close value for the next iteration lastCloseValue = closeValue; return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = openValue, High = highValue, Low = lowValue, Close = closeValue, Volume = Math.Abs(closeValue - openValue), // Volume as the change in value Ticks = slice.Ticks }; } private HistoryItemBar GenerateWhiteNoise(DateTime time, TimeSpan slice) { double volatility = 2; double meanReversionStrength = 0.1; double openNoise = random.NextDouble(); double open = previousClose + volatility * openNoise + meanReversionStrength * (meanPrice - previousClose); double closeNoise = random.NextDouble(); double close = open + volatility * closeNoise + meanReversionStrength * (meanPrice - open); // Determine High and Low double high = Math.Max(open, close); double low = Math.Min(open, close); // Add variation to High and Low double highNoise = Math.Abs(random.NextDouble()); high += volatility * highNoise; double lowNoise = Math.Abs(random.NextDouble()); low -= volatility * lowNoise; double volume = Math.Abs(random.NextDouble()) * 1000 + 100; previousClose = close; // Create the HistoryItemBar var historyItem = new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = open, High = high, Low = low, Close = close, Volume = volume, Ticks = slice.Ticks }; return historyItem; } private double previousClose = 50; private const double meanPrice = 50; private HistoryItemBar GeneratePinkNoise(DateTime time, TimeSpan slice) { double volatility = 2; double meanReversionStrength = 0.1; // Generate open price double openNoise = GeneratePinkNoiseValue(); double open = previousClose + volatility * openNoise + meanReversionStrength * (meanPrice - previousClose); // Generate close price double closeNoise = GeneratePinkNoiseValue(); double close = open + volatility * closeNoise + meanReversionStrength * (meanPrice - open); // Determine High and Low double high = Math.Max(open, close); double low = Math.Min(open, close); // Add variation to High and Low double highNoise = Math.Abs(GeneratePinkNoiseValue()); high += volatility * highNoise; double lowNoise = Math.Abs(GeneratePinkNoiseValue()); low -= volatility * lowNoise; double volume = Math.Abs(GeneratePinkNoiseValue()) * 1000 + 100; // Update previous close for the next iteration previousClose = close; return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = open, High = high, Low = low, Close = close, Volume = volume, Ticks = slice.Ticks }; } private const int NumOctaves = 6; private double[] pinkNoiseState = new double[NumOctaves]; private double GeneratePinkNoiseValue() { double total = 0; for (int i = 0; i < NumOctaves; i++) { double white = random.NextDouble() * 2 - 1; pinkNoiseState[i] = (pinkNoiseState[i] + white) * 0.5; total += pinkNoiseState[i] * Math.Pow(2, -i); } // Normalize return total / NumOctaves; } private double lastValue = 0; private HistoryItemBar GenerateBrownNoise(DateTime time, TimeSpan slice) { double dt = slice.TotalDays / 365.0; // Time step in years double sigma = 25.0; // Annual volatility double increment = GenerateGaussian(0, sigma * Math.Sqrt(dt)); double open = lastValue * (1 + GenerateGaussian(0, 0.05)); double close = open + increment; // Simulate intra-period high and low double high = Math.Max(open, close); high += high * Math.Abs(GenerateGaussian(0, 0.06)); double low = Math.Min(open, close); low -= low * Math.Abs(GenerateGaussian(0, 0.06)); lastValue = close; return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = open, High = high, Low = low, Close = close, Volume = Math.Abs(close - open) * 1000, // Simplified volume calculation Ticks = slice.Ticks }; } // Helper method to generate Gaussian distributed random numbers private double GenerateGaussian(double mean, double stdDev) { double u1 = 1.0 - random.NextDouble(); // Uniform(0,1] random doubles double u2 = 1.0 - random.NextDouble(); double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); return mean + stdDev * randStdNormal; } private double GBMLastClose = 100; // Starting price private double GBMMu = 0.05; // Annual drift private double GBMSigma = 0.2; // Annual volatility private HistoryItemBar GenerateGBM(DateTime time, TimeSpan slice) { // Convert time slice to years double dt = slice.TotalDays / 365.0; // Generate a random normal variable for the main price movement double epsilon = GenerateGaussian(0, 1); // Calculate the price movement using GBM equation double drift = (GBMMu - 0.5 * GBMSigma * GBMSigma) * dt; double diffusion = GBMSigma * Math.Sqrt(dt) * epsilon; double returnValue = Math.Exp(drift + diffusion); // Add variability between previous close and current open double openVariability = GBMLastClose * GBMSigma * Math.Sqrt(dt) * GenerateGaussian(0, 1) * 0.1; double open = GBMLastClose + openVariability; // Calculate new close price double close = open * returnValue; // Generate High and Low values double highLowRange = Math.Max(Math.Abs(close - open), GBMLastClose * GBMSigma * Math.Sqrt(dt) * Math.Abs(GenerateGaussian(0, 1))); double high = Math.Max(open, close) + highLowRange * 0.5; double low = Math.Min(open, close) - highLowRange * 0.5; // Generate volume (you may want to adjust this based on your needs) double volume = Math.Max(100, 1000 * Math.Abs(close - open) + 500 * GenerateGaussian(0, 1)); // Update last close for next iteration GBMLastClose = close; return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = open, High = high, Low = low, Close = close, Volume = volume, Ticks = slice.Ticks }; } private double FBMLastClose = 100; // Starting price private double FBMHurst = 0.85; // Hurst parameter (0.5 < H < 1 for persistent fBm) private double FBMSigma = 0.25; // Volatility parameter private double FBMDrift = 0.001; // drift private HistoryItemBar GenerateFBM(DateTime time, TimeSpan slice) { double dt = Math.Pow(slice.TotalDays / 365.0, 0.5); double epsilon = GenerateFractionalGaussianNoise(FBMHurst); double drift = FBMDrift * dt; double diffusion = FBMSigma * Math.Pow(dt, FBMHurst) * epsilon; double openVariability = FBMLastClose * FBMSigma * Math.Pow(dt, FBMHurst) * GenerateFractionalGaussianNoise(FBMHurst) * 0.1; double open = FBMLastClose + openVariability; double close = open * Math.Exp(drift + diffusion); double highLowRange = Math.Max(Math.Abs(close - open), FBMLastClose * FBMSigma * Math.Pow(dt, FBMHurst) * Math.Abs(GenerateFractionalGaussianNoise(FBMHurst)) * 2); double high = Math.Max(open, close) + highLowRange * 0.5; double low = Math.Min(open, close) - highLowRange * 0.5; double volume = Math.Max(100, 2000 * Math.Abs(close - open) + 1000 * Math.Abs(GenerateFractionalGaussianNoise(FBMHurst))); FBMLastClose = close; return new HistoryItemBar { TicksLeft = time.Ticks, TicksRight = time.Add(slice).Ticks - 1, Open = open, High = high, Low = low, Close = close, Volume = volume, Ticks = slice.Ticks }; } private double GenerateFractionalGaussianNoise(double hurst) { double sum = 0; int n = 1000; // Number of terms in the approximation for (int i = 1; i <= n; i++) { double ri = GenerateGaussian(0, 1); sum += (Math.Pow(i, hurst - 0.5) - Math.Pow(i - 1, hurst - 0.5)) * ri; } return sum / Math.Sqrt(n); } // Add other necessary overrides and implementations as needed } }