mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-12 23:58:04 +00:00
1139 lines
41 KiB
C#
1139 lines
41 KiB
C#
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<MessageExchange> exchanges;
|
|
private readonly List<MessageAsset> assets;
|
|
private readonly List<MessageSymbol> symbols;
|
|
|
|
public SyntheticVendor()
|
|
{
|
|
exchanges = new List<MessageExchange>
|
|
{
|
|
|
|
//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<MessageAsset>
|
|
{
|
|
new MessageAsset { Id = "USD", Name = "USD" },
|
|
|
|
};
|
|
|
|
symbols = new List<MessageSymbol>
|
|
{
|
|
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<VariableTick>
|
|
{
|
|
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<ConnectionInfo> { 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<MessageExchange> GetExchanges(CancellationToken token)
|
|
{
|
|
return exchanges;
|
|
}
|
|
|
|
public override IList<MessageAsset> GetAssets(CancellationToken token)
|
|
{
|
|
return assets;
|
|
}
|
|
|
|
public override IList<MessageSymbol> 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<IHistoryItem> LoadHistory(HistoryRequestParameters requestParameters)
|
|
{
|
|
var historyItems = new List<IHistoryItem>();
|
|
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<DateTime, TimeSpan, HistoryItemBar> 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<DateTime, TimeSpan, HistoryItemBar> 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
|
|
}
|
|
} |