Initial commit

This commit is contained in:
Michael Schimmel
2026-01-28 09:10:52 +01:00
commit a1d2e96f8a
513 changed files with 19503 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSLib", "MSLib\MSLib.csproj", "{81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
+450
View File
@@ -0,0 +1,450 @@
using System;
using System.Collections.Generic; // Required for List and Queue
using System.Linq; // Required for .Any() and .ToArray()
using System.Diagnostics; // For Debug.WriteLine, if needed for warnings/errors
namespace MS
{
// ICalculation interface defines a contract for updatable and resettable calculation objects.
// This allows different types of indicators or filters to be used interchangeably
// if they adhere to this structure.
public interface ICalculation
{
// Update processes a new data point (inputValue).
// inputValue: The new data point to incorporate into the calculation.
// commitState: If true, the internal state of the calculator is permanently updated.
// If false, the calculation is performed as a "peek" or "what-if" scenario
// without altering the persistent state of the calculator.
// Returns: The calculated value (e.g., median, MMI, filtered output) as a double.
// May return double.NaN if the calculation cannot be performed (e.g., insufficient data).
double Update(double inputValue, bool commitState = true);
// Reset re-initializes the calculator to its default state.
// This typically involves clearing any stored historical data or state variables,
// making the calculator ready for a new sequence of data.
void Reset();
}
// Median class calculates a rolling median over a specified period.
// The median is the middle value of a data set sorted in ascending order.
// This implementation allows for committing state changes or calculating a "what-if" median.
public class Median : ICalculation
{
private readonly int _period; // The number of data points in the rolling window.
private readonly List<double> _windowValues; // Stores the sorted values within the current window. Used for efficient median calculation.
private readonly Queue<double> _windowOrder; // Stores the values in insertion order to manage the rolling window (FIFO).
// Constructor for the Median calculator.
// period: The number of data points to include in the median calculation window. Must be positive.
public Median(int period)
{
if (period <= 0)
throw new ArgumentOutOfRangeException(nameof(period), "Period must be positive.");
_period = period;
_windowValues = new List<double>(_period); // Initialize with capacity for performance.
_windowOrder = new Queue<double>(_period); // Initialize with capacity.
}
// Updates the median calculation with a new data point (inputValue).
// If commitState is true, the inputValue is added to the window, and the oldest value is removed if the window is full.
// The median is then calculated from this updated (persisted) window.
// If commitState is false, the median is calculated based on a temporary window:
// - If the persisted window is full, it simulates replacing the oldest values with inputValue (current logic uses P-1 smallest).
// - If the persisted window is not full, it adds inputValue to the current persisted values.
// This temporary calculation does not alter the actual persisted state of _windowValues or _windowOrder.
public double Update(double inputValue, bool commitState = true)
{
List<double> tempWindowForCalc = new List<double>(); // Temporary list for performing the median calculation.
if (commitState) // Persist the new value and update the window.
{
_windowOrder.Enqueue(inputValue); // Add to FIFO queue to track age.
int insertIndex = _windowValues.BinarySearch(inputValue); // Find position in sorted list.
if (insertIndex < 0) insertIndex = ~insertIndex; // If not found, get complement for insertion index.
_windowValues.Insert(insertIndex, inputValue); // Insert into sorted list.
if (_windowOrder.Count > _period) // If window size exceeds period.
{
double oldestValue = _windowOrder.Dequeue(); // Remove oldest from FIFO queue.
_windowValues.Remove(oldestValue); // Remove oldest from sorted list (can be slow if list is large).
}
tempWindowForCalc.AddRange(_windowValues); // Calculation based on the actual, updated window.
}
else // Calculate "what-if" median without persisting state.
{
if (_windowValues.Any()) // If there are existing values.
{
if (_windowValues.Count == _period) // If the persisted window is full.
{
// Current "what-if" logic: takes the _period-1 smallest values from the current window
// and adds the new inputValue. This might not always reflect replacing the "oldest" actual item.
for (int i = 0; i < _period - 1; ++i) tempWindowForCalc.Add(_windowValues[i]);
}
else // Persisted window is not yet full.
{
tempWindowForCalc.AddRange(_windowValues); // Use all current values.
}
}
tempWindowForCalc.Add(inputValue); // Add the new input value to the temporary set.
}
if (!tempWindowForCalc.Any()) // If no data in the temporary window.
{
return double.NaN; // Cannot calculate median.
}
tempWindowForCalc.Sort(); // Median calculation requires a sorted list.
int n = tempWindowForCalc.Count;
double calculatedMedian;
if (n == 0) return double.NaN; // Should be caught by .Any() check above.
if (n % 2 == 0) // Even number of elements.
{
if (n >= 2) { // Median is the average of the two middle elements.
calculatedMedian = (tempWindowForCalc[n / 2 - 1] + tempWindowForCalc[n / 2]) / 2.0;
} else {
return double.NaN; // Not enough elements for a meaningful even median.
}
}
else // Odd number of elements.
{
calculatedMedian = tempWindowForCalc[n / 2]; // Median is the middle element.
}
return calculatedMedian;
}
// Resets the Median calculator to its initial state.
// Clears all stored values from the calculation window.
public void Reset()
{
_windowValues.Clear(); // Empties the sorted list of values.
_windowOrder.Clear(); // Empties the queue tracking insertion order.
}
}
// MMI (Market Meanness Index) class calculates a rolling index indicating if a market is trending or mean-reverting.
// It uses a Median calculator internally.
// The MMI value is typically between 0 and 100.
public class MMI : ICalculation
{
private readonly int _period; // The period for MMI and its internal Median calculation.
private readonly Median _medianCalculator; // Internal Median calculator instance.
private readonly Queue<double> _priceWindow; // Stores raw price values in insertion order for MMI logic.
// Constructor for the MMI calculator.
// period: The number of data points for the MMI window. Must be at least 2.
public MMI(int period)
{
if (period < 2) // MMI calculation requires at least 2 data points to compare.
throw new ArgumentOutOfRangeException(nameof(period), "Period must be at least 2 for MMI calculation.");
_period = period;
_medianCalculator = new Median(period); // Initialize the Median helper.
_priceWindow = new Queue<double>(period); // Initialize price queue.
}
// Updates the MMI calculation with a new data point (inputValue).
// It first updates its internal Median calculator.
// Then, it updates its own price window based on commitState.
// Finally, it calculates MMI based on the median and the price window.
// MMI is calculated as 100 * (nl + nh) / (period - 1), where:
// nl: count of prices > median and > previous price.
// nh: count of prices < median and < previous price.
// Returns NaN if the median is NaN or if the price window for MMI isn't full (_period size).
public double Update(double inputValue, bool commitState = true)
{
// Update the median, passing through the commitState.
double calculatedMedian = _medianCalculator.Update(inputValue, commitState);
double[] pricesForMmiLoop; // Array to hold prices for the MMI loop.
if (commitState) // Persist inputValue in MMI's price window.
{
_priceWindow.Enqueue(inputValue);
if (_priceWindow.Count > _period)
{
_priceWindow.Dequeue(); // Maintain window size.
}
pricesForMmiLoop = _priceWindow.ToArray(); // Use the actual, updated price window.
}
else // "What-if" scenario for MMI price window.
{
List<double> tempList = new List<double>();
if (_priceWindow.Any())
{
if (_priceWindow.Count == _period) // If full, simulate replacing the oldest.
{
var tempQueue = new Queue<double>(_priceWindow.Skip(1)); // Create a new queue, skipping the oldest.
tempQueue.Enqueue(inputValue); // Add the new value.
pricesForMmiLoop = tempQueue.ToArray();
}
else // Not full, just add to current MMI prices.
{
tempList.AddRange(_priceWindow);
tempList.Add(inputValue);
pricesForMmiLoop = tempList.ToArray();
}
}
else // Price window is empty.
{
tempList.Add(inputValue);
pricesForMmiLoop = tempList.ToArray();
}
}
// MMI can only be calculated if median is valid and the price window for the loop is full.
if (double.IsNaN(calculatedMedian) || pricesForMmiLoop.Length != _period)
{
return double.NaN;
}
int nl = 0; // Counter for "trending up" conditions.
int nh = 0; // Counter for "trending down" conditions.
// Loop starts from 1 because it compares price[i] with price[i-1].
for (int i = 1; i < _period; i++)
{
double priceInWindow = pricesForMmiLoop[i];
double prevPriceInWindow = pricesForMmiLoop[i - 1];
// Condition for nl: price is above median and current price is greater than previous price.
if (priceInWindow > calculatedMedian && priceInWindow > prevPriceInWindow) nl++;
// Condition for nh: price is below median and current price is less than previous price.
else if (priceInWindow < calculatedMedian && priceInWindow < prevPriceInWindow) nh++;
}
// Denominator (_period - 1) is safe because constructor enforces _period >= 2.
return 100.0 * (nl + nh) / (_period - 1);
}
// Resets the MMI calculator to its initial state.
// This involves resetting its internal Median calculator and clearing its own price window.
public void Reset()
{
_medianCalculator.Reset(); // Reset the dependent Median calculator.
_priceWindow.Clear(); // Clear the MMI's specific price data.
}
}
// LowPassFilter class implements a 2nd order IIR (Infinite Impulse Response) digital filter.
// It can be configured as various types of low-pass filters (Butterworth, Bessel, Chebyshev)
// using static factory methods.
// The filter equation is:
// y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]
public class LowPassFilter : ICalculation
{
// Filter coefficients for the numerator (feedforward)
private readonly double _b0, _b1, _b2;
// Filter coefficients for the denominator (feedback), signs are for the direct subtraction in formula.
private readonly double _a1, _a2;
// State variables storing previous input values.
private double _x1; // x[n-1]
private double _x2; // x[n-2]
// State variables storing previous output values.
private double _y1; // y[n-1]
private double _y2; // y[n-2]
// Constructor to initialize the filter with specific coefficients.
// Used by the static factory methods or for custom filter designs.
// b0_coeff, b1_coeff, b2_coeff: Numerator (feedforward) coefficients.
// a1_coeff, a2_coeff: Denominator (feedback) coefficients for the form y[n] = ... - a1*y[n-1] - a2*y[n-2].
public LowPassFilter(double b0_coeff, double b1_coeff, double b2_coeff, double a1_coeff, double a2_coeff)
{
_b0 = b0_coeff;
_b1 = b1_coeff;
_b2 = b2_coeff;
_a1 = a1_coeff; // Corresponds to a1 in y[n] = ... - a1*y[n-1]
_a2 = a2_coeff; // Corresponds to a2 in y[n] = ... - a2*y[n-2]
Reset(); // Initialize state variables to zero.
}
// Factory method to create a 2nd order Butterworth low-pass filter.
// Butterworth filters are known for their maximally flat passband response.
// cutoffFrequency: The -3dB cutoff frequency of the filter.
// samplingFrequency: The sampling frequency of the input signal.
public static LowPassFilter CreateButterworthLowPass(double cutoffFrequency, double samplingFrequency)
{
if (cutoffFrequency <= 0)
throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), "Cutoff frequency must be positive.");
if (samplingFrequency <= 0)
throw new ArgumentOutOfRangeException(nameof(samplingFrequency), "Sampling frequency must be positive.");
// Cutoff frequency must be less than Nyquist frequency (samplingFrequency / 2.0).
if (cutoffFrequency >= samplingFrequency / 2.0)
throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), $"Cutoff frequency ({cutoffFrequency}) must be strictly less than half the sampling frequency ({samplingFrequency / 2.0}).");
double b0_calc, b1_calc, b2_calc, a1_calc, a2_calc;
// Prewarp the cutoff frequency for the bilinear transform.
// C = tan(pi * fc / fs)
double C = Math.Tan(Math.PI * cutoffFrequency / samplingFrequency);
if (Math.Abs(C) < 1e-12) // Handle extremely low cutoff (approaching DC).
{
b0_calc = 0; b1_calc = 0; b2_calc = 0; // Effectively blocks AC signals.
// Coefficients for y[n] = 2*y[n-1] - y[n-2] (integrator-like behavior for output).
a1_calc = -2.0; // So that -a1_calc*y[n-1] becomes +2*y[n-1].
a2_calc = 1.0; // So that -a2_calc*y[n-2] becomes -1*y[n-2].
}
else
{
double sqrt2 = Math.Sqrt(2.0);
// Denominator factor for Butterworth coefficients after bilinear transform.
double D_butter = 1.0 / (1.0 + sqrt2 * C + C * C);
// Numerator coefficients
b0_calc = C * C * D_butter;
b1_calc = 2.0 * b0_calc;
b2_calc = b0_calc;
// Denominator coefficients (for 1 + a1_std*z^-1 + a2_std*z^-2)
// These are directly used as _a1, _a2 in the filter update equation.
a1_calc = 2.0 * (C * C - 1.0) * D_butter;
a2_calc = (1.0 - sqrt2 * C + C * C) * D_butter;
}
return new LowPassFilter(b0_calc, b1_calc, b2_calc, a1_calc, a2_calc);
}
// Factory method to create a 2nd order Bessel low-pass filter.
// Bessel filters are known for their maximally flat group delay (linear phase response in passband).
// cutoffFrequency: The -3dB cutoff frequency. Note: Bessel filter design often refers to other frequency points.
// This implementation aims for -3dB at the specified cutoffFrequency.
// samplingFrequency: The sampling frequency of the input signal.
public static LowPassFilter CreateBesselLowPass(double cutoffFrequency, double samplingFrequency)
{
if (cutoffFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), "Cutoff frequency must be positive.");
if (samplingFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(samplingFrequency), "Sampling frequency must be positive.");
if (cutoffFrequency >= samplingFrequency / 2.0) throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), $"Cutoff frequency ({cutoffFrequency}) must be strictly less than half the sampling frequency ({samplingFrequency / 2.0}).");
// C_bessel = tan(pi * fc / fs), prewarped frequency factor.
double C_bessel = Math.Tan(Math.PI * cutoffFrequency / samplingFrequency);
if (Math.Abs(C_bessel) < 1e-12) // Handle low cutoff.
{
return new LowPassFilter(0, 0, 0, -2.0, 1.0); // Similar to Butterworth low cutoff.
}
// Coefficients derived from analog Bessel prototype (s^2 + 3s + 3),
// frequency scaled for -3dB at cutoffFrequency, then transformed using bilinear method.
// omega_c_analog_warped = 2 * fs * tan(pi * fc / fs)
double omega_c_analog_warped = 2.0 * samplingFrequency * C_bessel;
// k_bessel_norm_3db is a scaling factor for the Bessel prototype's natural frequency
// to achieve the -3dB point at the desired warped analog cutoff. Value is approx. 1.3617.
double k_bessel_norm_3db = 1.3617070504; // Sqrt(3 * (Sqrt(5.0) - 1.0) / 2.0)
double omega_0_scaled = omega_c_analog_warped / k_bessel_norm_3db; // Scaled analog natural frequency for prototype.
// Analog prototype denominator: s^2 + A_s*s + B_s
// For Bessel n=2, scaled: s^2 + (3*omega_0_scaled)*s + (3*omega_0_scaled^2)
double as_coeff_analog = 3.0 * omega_0_scaled; // Coefficient for s-term.
double bs_coeff_analog = 3.0 * omega_0_scaled * omega_0_scaled; // Constant term (and numerator for DC gain 1).
// K_T_bilinear = 2 * fs, factor in bilinear transform s = K_T * (1-z^-1)/(1+z^-1) (if K_T is used for 2/T_sample)
// Here, K_T_bilinear is 2*fs.
double K_T_bilinear = 2.0 * samplingFrequency;
// Denominator after bilinear transform of H(s) = bs_coeff_analog / (s^2 + as_coeff_analog*s + bs_coeff_analog)
// gives A0_prime_denom = (K_T^2) + as_coeff_analog*K_T + bs_coeff_analog for normalization.
double A0_prime_denom = K_T_bilinear * K_T_bilinear + as_coeff_analog * K_T_bilinear + bs_coeff_analog;
if (Math.Abs(A0_prime_denom) < 1e-12) throw new InvalidOperationException("Denominator term A0_prime_denom is zero in Bessel filter calculation.");
// Digital filter coefficients b_i = Bi_prime / A0_prime_denom, a_i = Ai_prime_std / A0_prime_denom
double b0_calc = bs_coeff_analog / A0_prime_denom;
double b1_calc = (2.0 * bs_coeff_analog) / A0_prime_denom; // From bilinear transform structure.
double b2_calc = bs_coeff_analog / A0_prime_denom;
// Denominator coefficients for 1 + a1_std*z^-1 + a2_std*z^-2 form.
double a1_calc = (2.0 * bs_coeff_analog - 2.0 * K_T_bilinear * K_T_bilinear) / A0_prime_denom;
double a2_calc = (K_T_bilinear * K_T_bilinear - as_coeff_analog * K_T_bilinear + bs_coeff_analog) / A0_prime_denom;
return new LowPassFilter(b0_calc, b1_calc, b2_calc, a1_calc, a2_calc);
}
// Factory method to create a 2nd order Chebyshev Type I low-pass filter.
// Chebyshev Type I filters have a steeper roll-off than Butterworth, but exhibit ripple in the passband.
// cutoffFrequency: The frequency at which the passband ripple ends (e.g., gain drops by rippleDb).
// samplingFrequency: The sampling frequency of the input signal.
// rippleDb: The passband ripple in decibels (e.g., 0.5 dB, 1 dB). Must be positive.
public static LowPassFilter CreateChebyshevLowPass(double cutoffFrequency, double samplingFrequency, double rippleDb)
{
if (cutoffFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), "Cutoff frequency must be positive.");
if (samplingFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(samplingFrequency), "Sampling frequency must be positive.");
if (cutoffFrequency >= samplingFrequency / 2.0) throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), $"Cutoff frequency ({cutoffFrequency}) must be strictly less than half the sampling frequency ({samplingFrequency / 2.0}).");
if (rippleDb <= 0) throw new ArgumentOutOfRangeException(nameof(rippleDb), "Passband ripple must be positive.");
// C_cheby = tan(pi * fc / fs), prewarped frequency factor.
double C_cheby = Math.Tan(Math.PI * cutoffFrequency / samplingFrequency);
if (Math.Abs(C_cheby) < 1e-12) // Handle low cutoff.
{
return new LowPassFilter(0,0,0, -2.0, 1.0); // Similar to Butterworth low cutoff.
}
// Epsilon is derived from the passband ripple.
double epsilon = Math.Sqrt(Math.Pow(10, rippleDb / 10.0) - 1.0);
int N_order = 2; // Filter order is fixed at 2.
// Parameters for locating analog prototype poles on an ellipse.
// alpha_val determines the eccentricity of the ellipse.
double alpha_val = Math.Asinh(1.0 / epsilon) / N_order;
// For N=2, poles are at s_k = -sigma_k +/- j*omega_k.
// These are scaled by C_cheby (warped cutoff frequency) for the specific filter.
double sin_pi_over_2N = Math.Sin(Math.PI / (2.0 * N_order)); // sin(pi/4) for N=2
double cos_pi_over_2N = Math.Cos(Math.PI / (2.0 * N_order)); // cos(pi/4) for N=2
double sigma_k_scaled = Math.Sinh(alpha_val) * sin_pi_over_2N * C_cheby;
double omega_k_scaled = Math.Cosh(alpha_val) * cos_pi_over_2N * C_cheby;
// Analog prototype denominator: s^2 + A_s*s + B_s
// Derived from poles: (s + sigma_k_scaled)^2 + omega_k_scaled^2
double as_coeff_analog = 2.0 * sigma_k_scaled; // s-term coefficient.
double bs_coeff_analog = sigma_k_scaled * sigma_k_scaled + omega_k_scaled * omega_k_scaled; // Constant term.
// Analog numerator: For Chebyshev Type I, N=2 (even order), DC gain H(0) = 1.
// So, numerator constant of analog prototype is bs_coeff_analog.
double num_s_coeff = bs_coeff_analog;
double K_T_bilinear = 2.0 * samplingFrequency; // Factor for bilinear transform.
// Denominator for normalization after bilinear transform.
double A0_prime_denom = K_T_bilinear * K_T_bilinear + as_coeff_analog * K_T_bilinear + bs_coeff_analog;
if (Math.Abs(A0_prime_denom) < 1e-12) throw new InvalidOperationException("Denominator term A0_prime_denom is zero in Chebyshev filter calculation.");
// Digital filter coefficients.
double b0_calc = num_s_coeff / A0_prime_denom;
double b1_calc = (2.0 * num_s_coeff) / A0_prime_denom;
double b2_calc = num_s_coeff / A0_prime_denom;
double a1_calc = (2.0 * bs_coeff_analog - 2.0 * K_T_bilinear * K_T_bilinear) / A0_prime_denom;
double a2_calc = (K_T_bilinear * K_T_bilinear - as_coeff_analog * K_T_bilinear + bs_coeff_analog) / A0_prime_denom;
return new LowPassFilter(b0_calc, b1_calc, b2_calc, a1_calc, a2_calc);
}
// Updates the filter with a new input sample (inputValue).
// Applies the difference equation:
// y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]
// If commitState is true, the internal state variables (previous inputs and outputs) are updated.
// If false, the output is calculated using the current state, but the state is not advanced.
public double Update(double inputValue, bool commitState = true)
{
// Calculate the current output y[n] based on current input x[n] and previous states.
double output = _b0 * inputValue + _b1 * _x1 + _b2 * _x2 - _a1 * _y1 - _a2 * _y2;
if (commitState) // If true, update the filter's internal state.
{
// Shift previous inputs: x[n-2] = x[n-1], x[n-1] = x[n] (current inputValue).
_x2 = _x1;
_x1 = inputValue;
// Shift previous outputs: y[n-2] = y[n-1], y[n-1] = y[n] (current calculated output).
_y2 = _y1;
_y1 = output;
}
return output; // Return the filtered output.
}
// Resets the filter's internal state variables to zero.
// This clears any memory of previous input or output values, useful for starting a new filtering sequence.
public void Reset()
{
_x1 = 0; _x2 = 0; // Reset previous input states.
_y1 = 0; _y2 = 0; // Reset previous output states.
}
}
}
+9
View File
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
+286
View File
@@ -0,0 +1,286 @@
using System;
using System.Collections.Generic; // Required for List<T>
using System.IO; // For File operations
using System.Runtime.InteropServices; // Required for StructLayout and Marshal
namespace Zorro
{
/// <summary>
/// Provides generic methods for loading and saving arrays of structures
/// from/to binary files using marshalling.
/// This class is intended for internal use within this assembly.
/// </summary>
internal static class BinaryFileHandler
{
public static void SaveArrayAsBlock<T>(string filePath, T[] data) where T : struct
{
if (data == null || data.Length == 0)
{
File.WriteAllBytes(filePath, Array.Empty<byte>());
return;
}
int structSize = Marshal.SizeOf(typeof(T));
byte[] byteArray = new byte[data.Length * structSize];
IntPtr buffer = Marshal.AllocHGlobal(structSize);
try
{
for (int i = 0; i < data.Length; i++)
{
Marshal.StructureToPtr(data[i], buffer, false);
Marshal.Copy(buffer, byteArray, i * structSize, structSize);
}
File.WriteAllBytes(filePath, byteArray);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
public static T[]? LoadArrayAsBlock<T>(string filePath) where T : struct
{
if (!File.Exists(filePath))
{
return null;
}
byte[] byteArray = File.ReadAllBytes(filePath);
if (byteArray.Length == 0)
{
return Array.Empty<T>();
}
int structSize = Marshal.SizeOf(typeof(T));
if (byteArray.Length % structSize != 0)
{
throw new ArgumentException($"File size ({byteArray.Length} bytes) is not a valid multiple of the structure size ({structSize} bytes) for type {typeof(T).Name}. The file may be corrupted or not in the expected format.");
}
int itemCount = byteArray.Length / structSize;
T[] dataArray = new T[itemCount];
IntPtr buffer = Marshal.AllocHGlobal(structSize);
try
{
for (int i = 0; i < itemCount; i++)
{
Marshal.Copy(byteArray, i * structSize, buffer, structSize);
dataArray[i] = Marshal.PtrToStructure<T>(buffer);
}
}
finally
{
Marshal.FreeHGlobal(buffer);
}
return dataArray;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct ZorroT1
{
public double OADate;
public float Value;
/// <summary>
/// Returns the time as a UTC DateTime object.
/// Note: DateTime.FromOADate returns a DateTime with Kind=Unspecified.
/// We specify it as UTC as Zorro DATEs are GMT/UTC.
/// </summary>
public DateTime TimeUtc => DateTime.SpecifyKind(DateTime.FromOADate(OADate), DateTimeKind.Utc);
public ZorroT1(double oaDate, float value)
{
OADate = oaDate;
Value = value;
}
public ZorroT1(DateTime dateTime, float value)
{
// Ensure the DateTime is converted to its UTC equivalent for OADate
OADate = dateTime.ToUniversalTime().ToOADate();
Value = value;
}
public static void SaveToFile(string filePath, ZorroT1[] data) => BinaryFileHandler.SaveArrayAsBlock<ZorroT1>(filePath, data);
public static ZorroT1[]? LoadFromFile(string filePath) => BinaryFileHandler.LoadArrayAsBlock<ZorroT1>(filePath);
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct ZorroT2
{
public double OADate;
public float Price;
public float Volume;
public DateTime TimeUtc => DateTime.SpecifyKind(DateTime.FromOADate(OADate), DateTimeKind.Utc);
public ZorroT2(double oaDate, float price, float volume)
{
OADate = oaDate;
Price = price;
Volume = volume;
}
public ZorroT2(DateTime dateTime, float price, float volume)
{
OADate = dateTime.ToUniversalTime().ToOADate();
Price = price;
Volume = volume;
}
public static void SaveToFile(string filePath, ZorroT2[] data) => BinaryFileHandler.SaveArrayAsBlock<ZorroT2>(filePath, data);
public static ZorroT2[]? LoadFromFile(string filePath) => BinaryFileHandler.LoadArrayAsBlock<ZorroT2>(filePath);
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct ZorroT6
{
public double OADate;
public float High;
public float Low;
public float Open;
public float Close;
public float AdditionalValue1;
public float AdditionalValue2;
public DateTime TimeUtc => DateTime.SpecifyKind(DateTime.FromOADate(OADate), DateTimeKind.Utc);
public ZorroT6(double oaDate, float high, float low, float open, float close, float additionalValue1, float additionalValue2)
{
OADate = oaDate;
High = high;
Low = low;
Open = open;
Close = close;
AdditionalValue1 = additionalValue1;
AdditionalValue2 = additionalValue2;
}
public ZorroT6(DateTime dateTime, float high, float low, float open, float close, float additionalValue1, float additionalValue2)
{
OADate = dateTime.ToUniversalTime().ToOADate();
High = high;
Low = low;
Open = open;
Close = close;
AdditionalValue1 = additionalValue1;
AdditionalValue2 = additionalValue2;
}
public static void SaveToFile(string filePath, ZorroT6[] data) => BinaryFileHandler.SaveArrayAsBlock<ZorroT6>(filePath, data);
public static ZorroT6[]? LoadFromFile(string filePath) => BinaryFileHandler.LoadArrayAsBlock<ZorroT6>(filePath);
}
// --- Builder Classes for Incremental Array Construction ---
public class ZorroT1ArrayBuilder
{
private readonly List<ZorroT1> _items;
public ZorroT1ArrayBuilder(int initialCapacity = 0)
{
_items = initialCapacity > 0 ? new List<ZorroT1>(initialCapacity) : new List<ZorroT1>();
}
public ZorroT1ArrayBuilder Add(DateTime time, float value)
{
// Constructor of ZorroT1 now handles UTC conversion
_items.Add(new ZorroT1(time, value));
return this;
}
public ZorroT1ArrayBuilder Add(double oaDate, float value)
{
_items.Add(new ZorroT1(oaDate, value));
return this;
}
public ZorroT1ArrayBuilder Add(ZorroT1 item)
{
_items.Add(item);
return this;
}
public ZorroT1[] ToArray() => _items.ToArray();
public void Clear() => _items.Clear();
public int Count => _items.Count;
public void SaveToFile(string filePath)
{
ZorroT1.SaveToFile(filePath, _items.ToArray());
}
}
public class ZorroT2ArrayBuilder
{
private readonly List<ZorroT2> _items;
public ZorroT2ArrayBuilder(int initialCapacity = 0)
{
_items = initialCapacity > 0 ? new List<ZorroT2>(initialCapacity) : new List<ZorroT2>();
}
public ZorroT2ArrayBuilder Add(DateTime time, float price, float volume)
{
_items.Add(new ZorroT2(time, price, volume));
return this;
}
public ZorroT2ArrayBuilder Add(double oaDate, float price, float volume)
{
_items.Add(new ZorroT2(oaDate, price, volume));
return this;
}
public ZorroT2ArrayBuilder Add(ZorroT2 item)
{
_items.Add(item);
return this;
}
public ZorroT2[] ToArray() => _items.ToArray();
public void Clear() => _items.Clear();
public int Count => _items.Count;
public void SaveToFile(string filePath)
{
ZorroT2.SaveToFile(filePath, _items.ToArray());
}
}
public class ZorroT6ArrayBuilder
{
private readonly List<ZorroT6> _items;
public ZorroT6ArrayBuilder(int initialCapacity = 0)
{
_items = initialCapacity > 0 ? new List<ZorroT6>(initialCapacity) : new List<ZorroT6>();
}
public ZorroT6ArrayBuilder Add(DateTime time, float high, float low, float open, float close, float additionalValue1, float additionalValue2)
{
_items.Add(new ZorroT6(time, high, low, open, close, additionalValue1, additionalValue2));
return this;
}
public ZorroT6ArrayBuilder Add(double oaDate, float high, float low, float open, float close, float additionalValue1, float additionalValue2)
{
_items.Add(new ZorroT6(oaDate, high, low, open, close, additionalValue1, additionalValue2));
return this;
}
public ZorroT6ArrayBuilder Add(ZorroT6 item)
{
_items.Add(item);
return this;
}
public ZorroT6[] ToArray() => _items.ToArray();
public void Clear() => _items.Clear();
public int Count => _items.Count;
public void SaveToFile(string filePath)
{
ZorroT6.SaveToFile(filePath, _items.ToArray());
}
}
}
@@ -0,0 +1,23 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v6.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v6.0": {
"MSLib/1.0.0": {
"runtime": {
"MSLib.dll": {}
}
}
}
},
"libraries": {
"MSLib/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("MSLib")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("MSLib")]
[assembly: System.Reflection.AssemblyTitleAttribute("MSLib")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -0,0 +1 @@
5bd6cf3423e8e65d9421c371ad2c6b9a30b6eb1d
@@ -0,0 +1,10 @@
is_global = true
build_property.TargetFramework = net6.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = MSLib
build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\
@@ -0,0 +1,8 @@
// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
@@ -0,0 +1 @@
375eb7427a13a6a1a036f49ce73cc2a66452e118
@@ -0,0 +1,12 @@
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\bin\Debug\net6.0\MSLib.deps.json
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\bin\Debug\net6.0\MSLib.dll
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\bin\Debug\net6.0\MSLib.pdb
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.csproj.AssemblyReference.cache
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.GeneratedMSBuildEditorConfig.editorconfig
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.AssemblyInfoInputs.cache
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.AssemblyInfo.cs
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.csproj.CoreCompileInputs.cache
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.dll
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\refint\MSLib.dll
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.pdb
C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\ref\MSLib.dll
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,60 @@
{
"format": 1,
"restore": {
"C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj": {}
},
"projects": {
"C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj",
"projectName": "MSLib",
"projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj",
"packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\",
"outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\obj\\",
"projectStyle": "PackageReference",
"configFilePaths": [
"C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config"
],
"originalTargetFrameworks": [
"net6.0"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net6.0": {
"targetAlias": "net6.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
}
},
"frameworks": {
"net6.0": {
"targetAlias": "net6.0",
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json"
}
}
}
}
}
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Brummel\.nuget\packages\</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.1.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\Brummel\.nuget\packages\" />
</ItemGroup>
</Project>
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
@@ -0,0 +1,65 @@
{
"version": 3,
"targets": {
"net6.0": {}
},
"libraries": {},
"projectFileDependencyGroups": {
"net6.0": []
},
"packageFolders": {
"C:\\Users\\Brummel\\.nuget\\packages\\": {}
},
"project": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj",
"projectName": "MSLib",
"projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj",
"packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\",
"outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\obj\\",
"projectStyle": "PackageReference",
"configFilePaths": [
"C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config"
],
"originalTargetFrameworks": [
"net6.0"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net6.0": {
"targetAlias": "net6.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
}
},
"frameworks": {
"net6.0": {
"targetAlias": "net6.0",
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json"
}
}
}
}
@@ -0,0 +1,8 @@
{
"version": 2,
"dgSpecHash": "Oo9DgV2PMMwBLALF97djBTxT9Wgbo3xT86Zo8aWAwrxLBxF4zrxrwtxR5/TCIXpFczNFbEDKftQZxvjl9Y6BHQ==",
"success": true,
"projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj",
"expectedPackageFiles": [],
"logs": []
}