пятница, 10 октября 2025 г.

25.10.10, Deep, Seek, Random, Normal, Generator, Tests, Main,

25.10.10, Random, Normal, Generator, Tests, Main, 

F:\Projects\VS\GS.Math.25\GS.Math.25.sln

Отличная задача! Создам полную систему для генерации и тестирования нормального распределения.

1. Генератор нормального распределения (Box-Muller)

csharp
using System;
using System.Collections.Generic;
using System.Linq;

public class NormalDistributionGenerator
{
    private readonly Random _random;
    private readonly double _mean;
    private readonly double _stdDev;
    private bool _hasSpareValue;
    private double _spareValue;

    public NormalDistributionGenerator(double mean = 0, double stdDev = 1, int? seed = null)
    {
        _mean = mean;
        _stdDev = stdDev;
        _random = seed.HasValue ? new Random(seed.Value) : new Random();
        _hasSpareValue = false;
        _spareValue = 0;
    }

    // Метод Box-Muller для генерации нормального распределения
    public double Next()
    {
        if (_hasSpareValue)
        {
            _hasSpareValue = false;
            return _spareValue * _stdDev + _mean;
        }

        double u, v, s;
        do
        {
            u = _random.NextDouble() * 2 - 1; // [-1, 1]
            v = _random.NextDouble() * 2 - 1; // [-1, 1]
            s = u * u + v * v;
        } while (s >= 1 || s == 0);

        s = Math.Sqrt(-2.0 * Math.Log(s) / s);
        _spareValue = v * s;
        _hasSpareValue = true;

        return u * s * _stdDev + _mean;
    }

    public double[] GenerateSample(int size)
    {
        var sample = new double[size];
        for (int i = 0; i < size; i++)
        {
            sample[i] = Next();
        }
        return sample;
    }
}

2. Статистические функции для тестов

csharp
public static class StatisticalFunctions
{
    // Функция ошибок (Error Function)
    public static double Erf(double x)
    {
        // approximation using Abramowitz and Stegun formula 7.1.26
        double a1 = 0.254829592;
        double a2 = -0.284496736;
        double a3 = 1.421413741;
        double a4 = -1.453152027;
        double a5 = 1.061405429;
        double p = 0.3275911;

        int sign = x < 0 ? -1 : 1;
        x = Math.Abs(x);

        double t = 1.0 / (1.0 + p * x);
        double y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.Exp(-x * x);

        return sign * y;
    }

    // Нормальная функция распределения (CDF)
    public static double NormalCdf(double x, double mean = 0, double stdDev = 1)
    {
        return 0.5 * (1 + Erf((x - mean) / (stdDev * Math.Sqrt(2))));
    }

    // Гамма-функция
    public static double Gamma(double x)
    {
        // Простая реализация через ланчос
        if (x <= 0) throw new ArgumentException("x must be positive");
        
        double[] p = {
            676.5203681218851,
            -1259.1392167224028,
            771.32342877765313,
            -176.61502916214059,
            12.507343278686905,
            -0.13857109526572012,
            9.9843695780195716e-6,
            1.5056327351493116e-7
        };
        
        if (x < 0.5)
            return Math.PI / (Math.Sin(Math.PI * x) * Gamma(1 - x));
        
        x -= 1;
        double a = 0.99999999999980993;
        double t = x + 7.5;
        
        for (int i = 0; i < p.Length; i++)
        {
            a += p[i] / (x + i + 1);
        }
        
        return Math.Sqrt(2 * Math.PI) * Math.Pow(t, x + 0.5) * Math.Exp(-t) * a;
    }

    // Неполная гамма-функция
    public static double IncompleteGamma(double s, double x)
    {
        // Реализация через ряд
        if (x < 0) return double.NaN;
        if (x == 0) return 0;
        
        const int maxIterations = 100;
        const double epsilon = 1e-10;
        
        double term = 1.0 / s;
        double sum = term;
        
        for (int k = 1; k < maxIterations; k++)
        {
            term *= x / (s + k);
            sum += term;
            if (Math.Abs(term) < epsilon) break;
        }
        
        return Math.Pow(x, s) * Math.Exp(-x) * sum;
    }

    // Функция хи-квадрат распределения
    public static double ChiSquareCdf(double x, double degreesOfFreedom)
    {
        if (x <= 0) return 0;
        return IncompleteGamma(degreesOfFreedom / 2, x / 2) / Gamma(degreesOfFreedom / 2);
    }
}

3. Тест Колмогорова-Смирнова

csharp
public class KolmogorovSmirnovTest
{
    public static (double Statistic, double PValue) Test(double[] sample, double mean, double stdDev)
    {
        var sortedSample = sample.OrderBy(x => x).ToArray();
        int n = sortedSample.Length;
        
        double dPlus = 0;
        double dMinus = 0;
        
        for (int i = 0; i < n; i++)
        {
            double empirical = (i + 1.0) / n;
            double theoretical = StatisticalFunctions.NormalCdf(sortedSample[i], mean, stdDev);
            
            dPlus = Math.Max(dPlus, empirical - theoretical);
            dMinus = Math.Max(dMinus, theoretical - (double)i / n);
        }
        
        double d = Math.Max(dPlus, dMinus);
        double pValue = CalculatePValue(d, n);
        
        return (d, pValue);
    }
    
    private static double CalculatePValue(double d, int n)
    {
        // Аппроксимация p-value для теста Колмогорова-Смирнова
        if (n <= 0) return 1;
        
        double sqrtN = Math.Sqrt(n);
        double x = d * (sqrtN + 0.12 + 0.11 / sqrtN);
        
        if (x <= 0) return 1;
        if (x >= 3.0) return 0;
        
        double pValue = 0;
        for (int k = 1; k <= 100; k++)
        {
            pValue += Math.Exp(-2 * k * k * x * x);
        }
        
        return 2 * pValue;
    }
}

4. Тест хи-квадрат

csharp
public class ChiSquareTest
{
    public static (double Statistic, double PValue, int DegreesOfFreedom) Test(
        double[] sample, double mean, double stdDev, int bins = 10)
    {
        if (bins < 2) throw new ArgumentException("Number of bins must be at least 2");
        
        // Определяем границы бинов
        var min = sample.Min();
        var max = sample.Max();
        var binWidth = (max - min) / bins;
        
        // Наблюдаемые частоты
        var observed = new int[bins];
        foreach (var value in sample)
        {
            int binIndex = Math.Min((int)((value - min) / binWidth), bins - 1);
            observed[binIndex]++;
        }
        
        // Ожидаемые частоты для нормального распределения
        var expected = new double[bins];
        for (int i = 0; i < bins; i++)
        {
            double left = min + i * binWidth;
            double right = min + (i + 1) * binWidth;
            expected[i] = sample.Length * (
                StatisticalFunctions.NormalCdf(right, mean, stdDev) - 
                StatisticalFunctions.NormalCdf(left, mean, stdDev)
            );
        }
        
        // Вычисляем статистику хи-квадрат
        double chiSquare = 0;
        for (int i = 0; i < bins; i++)
        {
            if (expected[i] > 0)
            {
                chiSquare += Math.Pow(observed[i] - expected[i], 2) / expected[i];
            }
        }
        
        // Степени свободы: bins - 1 - 2 (два параметра оценены из данных)
        int degreesOfFreedom = bins - 3;
        double pValue = 1 - StatisticalFunctions.ChiSquareCdf(chiSquare, degreesOfFreedom);
        
        return (chiSquare, pValue, degreesOfFreedom);
    }
}

5. Тест Шапиро-Уилка (упрощенная версия)

csharp
public class ShapiroWilkTest
{
    // Коэффициенты для теста Шапиро-Уилка (для n <= 50)
    private static readonly Dictionary<int, double[]> Coefficients = new()
    {
        [3] = new double[] { 0.7071 },
        [4] = new double[] { 0.6872, 0.1677 },
        [5] = new double[] { 0.6646, 0.2413 },
        [6] = new double[] { 0.6431, 0.2806, 0.0875 },
        [7] = new double[] { 0.6233, 0.3031, 0.1401 },
        [8] = new double[] { 0.6052, 0.3164, 0.1743, 0.0561 },
        [9] = new double[] { 0.5888, 0.3244, 0.1976, 0.0947 },
        [10] = new double[] { 0.5739, 0.3291, 0.2141, 0.1224 },
        // Для демонстрации - в реальности нужны все коэффициенты до 50
    };

    public static (double Statistic, double PValue) Test(double[] sample)
    {
        var sorted = sample.OrderBy(x => x).ToArray();
        int n = sorted.Length;
        
        if (n < 3 || n > 50)
        {
            throw new ArgumentException("Shapiro-Wilk test requires sample size between 3 and 50");
        }
        
        if (!Coefficients.ContainsKey(n))
        {
            throw new ArgumentException($"Coefficients not available for sample size {n}");
        }
        
        // Вычисляем статистику W
        double w = CalculateWStatistic(sorted, n);
        
        // Аппроксимируем p-value (упрощенно)
        double pValue = ApproximatePValue(w, n);
        
        return (w, pValue);
    }
    
    private static double CalculateWStatistic(double[] sorted, int n)
    {
        var a = Coefficients[n];
        int k = n % 2 == 0 ? n / 2 : (n - 1) / 2;
        
        double numerator = 0;
        for (int i = 0; i < k; i++)
        {
            numerator += a[i] * (sorted[n - 1 - i] - sorted[i]);
        }
        
        double denominator = 0;
        double mean = sorted.Average();
        foreach (var x in sorted)
        {
            denominator += Math.Pow(x - mean, 2);
        }
        
        return Math.Pow(numerator, 2) / denominator;
    }
    
    private static double ApproximatePValue(double w, int n)
    {
        // Упрощенная аппроксимация p-value
        // В реальной реализации используются табличные значения
        if (w > 0.95) return 0.9;
        if (w > 0.90) return 0.7;
        if (w > 0.85) return 0.5;
        if (w > 0.80) return 0.3;
        if (w > 0.75) return 0.1;
        return 0.01;
    }
}

6. Полная система тестирования

csharp
public class NormalityTester
{
    private readonly double[] _sample;
    private readonly double _mean;
    private readonly double _stdDev;

    public NormalityTester(double[] sample)
    {
        _sample = sample ?? throw new ArgumentNullException(nameof(sample));
        _mean = sample.Average();
        _stdDev = CalculateStdDev(sample, _mean);
    }

    private double CalculateStdDev(double[] values, double mean)
    {
        double sumSquares = 0;
        foreach (var value in values)
        {
            sumSquares += Math.Pow(value - mean, 2);
        }
        return Math.Sqrt(sumSquares / (values.Length - 1));
    }

    public void RunAllTests(double significanceLevel = 0.05)
    {
        Console.WriteLine("=== ТЕСТИРОВАНИЕ НОРМАЛЬНОСТИ РАСПРЕДЕЛЕНИЯ ===");
        Console.WriteLine($"Размер выборки: {_sample.Length}");
        Console.WriteLine($"Выборочное среднее: {_mean:F4}");
        Console.WriteLine($"Выборочное СКО: {_stdDev:F4}");
        Console.WriteLine($"Уровень значимости: {significanceLevel}");
        Console.WriteLine();

        // Тест Колмогорова-Смирнова
        var (ksStat, ksPValue) = KolmogorovSmirnovTest.Test(_sample, _mean, _stdDev);
        Console.WriteLine("1. ТЕСТ КОЛМОГОРОВА-СМИРНОВА:");
        Console.WriteLine($"   Статистика: {ksStat:F4}");
        Console.WriteLine($"   P-value: {ksPValue:F4}");
        Console.WriteLine($"   Вывод: {(ksPValue > significanceLevel ? "Нормальность НЕ отвергается" : "Нормальность ОТВЕРГАЕТСЯ")}");
        Console.WriteLine();

        // Тест хи-квадрат
        var (chiStat, chiPValue, df) = ChiSquareTest.Test(_sample, _mean, _stdDev, bins: 10);
        Console.WriteLine("2. ТЕСТ ХИ-КВАДРАТ:");
        Console.WriteLine($"   Статистика: {chiStat:F4}");
        Console.WriteLine($"   P-value: {chiPValue:F4}");
        Console.WriteLine($"   Степени свободы: {df}");
        Console.WriteLine($"   Вывод: {(chiPValue > significanceLevel ? "Нормальность НЕ отвергается" : "Нормальность ОТВЕРГАЕТСЯ")}");
        Console.WriteLine();

        // Тест Шапиро-Уилка (для небольших выборок)
        if (_sample.Length >= 3 && _sample.Length <= 50)
        {
            try
            {
                var (swStat, swPValue) = ShapiroWilkTest.Test(_sample);
                Console.WriteLine("3. ТЕСТ ШАПИРО-УИЛКА:");
                Console.WriteLine($"   Статистика: {swStat:F4}");
                Console.WriteLine($"   P-value: {swPValue:F4}");
                Console.WriteLine($"   Вывод: {(swPValue > significanceLevel ? "Нормальность НЕ отвергается" : "Нормальность ОТВЕРГАЕТСЯ")}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"3. ТЕСТ ШАПИРО-УИЛКА: {ex.Message}");
            }
        }
        else
        {
            Console.WriteLine("3. ТЕСТ ШАПИРО-УИЛКА: Неприменим для данной выборки (требуется 3 ≤ n ≤ 50)");
        }

        Console.WriteLine();
        Console.WriteLine("=== СВОДКА ===");
        Console.WriteLine($"Выборка {(IsNormal(ksPValue, chiPValue, significanceLevel) ? "соответствует" : "НЕ соответствует")} нормальному распределению");
    }

    private bool IsNormal(double ksPValue, double chiPValue, double significanceLevel)
    {
        return ksPValue > significanceLevel && chiPValue > significanceLevel;
    }
}

7. Пример использования

csharp
class Program
{
    static void Main()
    {
        // Генерируем выборку нормального распределения
        var generator = new NormalDistributionGenerator(mean: 10, stdDev: 2, seed: 42);
        double[] sample = generator.GenerateSample(1000);

        // Выводим описательную статистику
        Console.WriteLine("Сгенерированная выборка:");
        Console.WriteLine($"Минимум: {sample.Min():F4}");
        Console.WriteLine($"Максимум: {sample.Max():F4}");
        Console.WriteLine($"Среднее: {sample.Average():F4}");
        Console.WriteLine($"Медиана: {CalculateMedian(sample):F4}");
        Console.WriteLine($"СКО: {CalculateStdDev(sample):F4}");
        Console.WriteLine();

        // Тестируем на нормальность
        var tester = new NormalityTester(sample);
        tester.RunAllTests(significanceLevel: 0.05);

        // Дополнительно: тест с заведомо ненормальной выборкой
        Console.WriteLine("\n" + new string('=', 50));
        Console.WriteLine("ТЕСТ С НЕНОРМАЛЬНОЙ ВЫБОРКОЙ (равномерное распределение):");
        
        var uniformSample = GenerateUniformSample(1000, 5, 15);
        var uniformTester = new NormalityTester(uniformSample);
        uniformTester.RunAllTests(significanceLevel: 0.05);
    }

    static double CalculateMedian(double[] values)
    {
        var sorted = values.OrderBy(x => x).ToArray();
        int n = sorted.Length;
        return n % 2 == 0 ? (sorted[n/2 - 1] + sorted[n/2]) / 2 : sorted[n/2];
    }

    static double CalculateStdDev(double[] values)
    {
        double mean = values.Average();
        double sumSquares = values.Sum(x => Math.Pow(x - mean, 2));
        return Math.Sqrt(sumSquares / (values.Length - 1));
    }

    static double[] GenerateUniformSample(int size, double min, double max)
    {
        var random = new Random(42);
        var sample = new double[size];
        for (int i = 0; i < size; i++)
        {
            sample[i] = min + random.NextDouble() * (max - min);
        }
        return sample;
    }
}

Особенности реализации:

  1. Box-Muller алгоритм - эффективная генерация нормальных чисел

  2. Три статистических теста - комплексная проверка нормальности

  3. Обработка граничных случаев - проверка размеров выборок

  4. Визуализация результатов - понятные выводы

  5. Сравнение с ненормальным распределением - демонстрация работы тестов

Это полная система для генерации и тестирования нормальных распределений! 🎯

Спасибо, великолепно. Я неоднократно пытался строить Генератор случайных чисел Нормального распределения для симуляции биржевых данных. И я всегда натыкался на одну проблемму. Котировки, как правило кучкуются примерно в одной области и потихоньку дрейфуют в том или ином напрвалении. Если Невооруженным глазом посмотреть, то котировки образуюн некоторые каналы (расширяющиеся, сужающиеся). Но всегда это похоже на канал, то есть некоторая локальная область. А когда я генерю Нормальное распределение своими не очень точными методами, то у меня часто получаются точки из противоположных квантилей. То есть не локальная область, а просто шарахание из стороны в сторону. То есть не получается генерить котировки из такой как-бы локальной области. Можешь ли ты сделать Генератор случайных чисел Нормального распределения, чтобы это было бы похоже котировки биржи. Не знаю донес ли я идею.

Отлично понимаю вашу проблему! Вы описываете нестационарный процесс с локальной автокорреляцией, а обычное нормальное распределение дает стационарный белый шум. Давайте создадим генератор, который имитирует реальные котировки.

1. Модель с дрейфом и локальной волатильностью

csharp
public class RealisticStockPriceGenerator
{
    private readonly NormalDistributionGenerator _normalGenerator;
    private readonly Random _random;
    private double _currentPrice;
    private double _currentDrift;
    private double _currentVolatility;
    private readonly double _meanReversionSpeed;
    private readonly double _volatilityMeanReversion;
    
    public RealisticStockPriceGenerator(double initialPrice, int? seed = null)
    {
        _normalGenerator = new NormalDistributionGenerator(seed: seed);
        _random = seed.HasValue ? new Random(seed.Value) : new Random();
        _currentPrice = initialPrice;
        _currentDrift = 0;
        _currentVolatility = 0.02; // 2% волатильность
        _meanReversionSpeed = 0.05; // Скорость возврата к среднему
        _volatilityMeanReversion = 0.1; // Скорость изменения волатильности
    }
    
    public double NextPrice()
    {
        // 1. Обновляем дрейф (медленно меняется)
        _currentDrift = _currentDrift * 0.95 + _normalGenerator.Next() * 0.0005;
        
        // 2. Обновляем волатильность (кластеризация волатильности)
        _currentVolatility = Math.Max(0.001, 
            _currentVolatility * 0.9 + Math.Abs(_normalGenerator.Next()) * 0.01);
        
        // 3. Генерируем возврат с учетом текущего дрейфа и волатильности
        double returns = _currentDrift + _currentVolatility * _normalGenerator.Next();
        
        // 4. Применяем к цене (геометрическое броуновское движение)
        _currentPrice *= Math.Exp(returns);
        
        return _currentPrice;
    }
    
    public double[] GeneratePriceSeries(int count)
    {
        var prices = new double[count];
        for (int i = 0; i < count; i++)
        {
            prices[i] = NextPrice();
        }
        return prices;
    }
}

2. Модель с каналами и поддержкой/сопротивлением

csharp
public class ChannelBasedPriceGenerator
{
    private readonly NormalDistributionGenerator _normalGenerator;
    private double _currentPrice;
    private double _channelCenter;
    private double _channelWidth;
    private double _trend;
    private int _timeInChannel;
    
    public ChannelBasedPriceGenerator(double initialPrice, int? seed = null)
    {
        _normalGenerator = new NormalDistributionGenerator(seed: seed);
        _currentPrice = initialPrice;
        _channelCenter = initialPrice;
        _channelWidth = initialPrice * 0.05; // 5% ширина канала
        _trend = 0;
        _timeInChannel = 0;
    }
    
    public double NextPrice()
    {
        _timeInChannel++;
        
        // Раз в 100-200 шагов меняем канал
        if (_timeInChannel > 100 + _normalGenerator.Next() * 100)
        {
            CreateNewChannel();
        }
        
        // Локальные колебания внутри канала
        double localMove = _normalGenerator.Next() * _channelWidth * 0.1;
        
        // Притяжение к центру канала (сила поддержки/сопротивления)
        double distanceToCenter = _channelCenter - _currentPrice;
        double meanReversionForce = distanceToCenter * 0.1;
        
        // Трендовая составляющая
        double trendMove = _trend;
        
        double newPrice = _currentPrice + localMove + meanReversionForce + trendMove;
        
        // Отскок от границ канала
        if (Math.Abs(newPrice - _channelCenter) > _channelWidth)
        {
            localMove *= -0.5; // Отскок
            newPrice = _currentPrice + localMove + meanReversionForce + trendMove;
        }
        
        _currentPrice = newPrice;
        return _currentPrice;
    }
    
    private void CreateNewChannel()
    {
        // Новый центр канала - случайное смещение ±10%
        double shift = (_normalGenerator.Next() * 0.2 - 0.1) * _channelCenter;
        _channelCenter += shift;
        
        // Новая ширина канала 3-8%
        _channelWidth = _channelCenter * (0.03 + _normalGenerator.Next() * 0.05);
        
        // Новый тренд (очень медленный)
        _trend = _normalGenerator.Next() * _channelCenter * 0.0001;
        
        _timeInChannel = 0;
    }
    
    public (double[] prices, double[] centers, double[] widths) GenerateSeriesWithChannels(int count)
    {
        var prices = new double[count];
        var centers = new double[count];
        var widths = new double[count];
        
        for (int i = 0; i < count; i++)
        {
            prices[i] = NextPrice();
            centers[i] = _channelCenter;
            widths[i] = _channelWidth;
        }
        
        return (prices, centers, widths);
    }
}

3. Продвинутая модель с памятью и кластеризацией

csharp
public class AdvancedPriceGenerator
{
    private readonly NormalDistributionGenerator _normalGenerator;
    private readonly List<double> _priceHistory;
    private readonly int _memorySize;
    private double _currentPrice;
    private double _localMean;
    private double _localVolatility;
    
    public AdvancedPriceGenerator(double initialPrice, int memorySize = 50, int? seed = null)
    {
        _normalGenerator = new NormalDistributionGenerator(seed: seed);
        _priceHistory = new List<double> { initialPrice };
        _memorySize = memorySize;
        _currentPrice = initialPrice;
        _localMean = initialPrice;
        _localVolatility = initialPrice * 0.02;
    }
    
    public double NextPrice()
    {
        // Обновляем локальную статистику
        UpdateLocalStatistics();
        
        // Генерируем возврат, зависящий от предыдущих значений
        double returns = GenerateCorrelatedReturns();
        
        // Применяем к цене
        _currentPrice *= Math.Exp(returns);
        _priceHistory.Add(_currentPrice);
        
        // Поддерживаем размер истории
        if (_priceHistory.Count > _memorySize)
            _priceHistory.RemoveAt(0);
            
        return _currentPrice;
    }
    
    private void UpdateLocalStatistics()
    {
        if (_priceHistory.Count < 10) return;
        
        // Локальное среднее (скользящее среднее)
        _localMean = _priceHistory.Average();
        
        // Локальная волатильность (на основе недавних данных)
        var recentPrices = _priceHistory.TakeLast(20).ToArray();
        double mean = recentPrices.Average();
        double variance = recentPrices.Average(p => Math.Pow(p - mean, 2));
        _localVolatility = Math.Sqrt(variance) / mean;
        
        // Ограничиваем волатильность
        _localVolatility = Math.Max(0.005, Math.Min(0.15, _localVolatility));
    }
    
    private double GenerateCorrelatedReturns()
    {
        // Автокорреляция - текущее значение зависит от предыдущих
        double correlatedNoise = 0;
        double totalWeight = 0;
        
        for (int i = 1; i <= Math.Min(5, _priceHistory.Count - 1); i++)
        {
            double weight = Math.Exp(-i * 0.5); // Экспоненциально убывающие веса
            double pastReturn = Math.Log(_priceHistory[^i] / _priceHistory[^(i + 1)]);
            correlatedNoise += pastReturn * weight;
            totalWeight += weight;
        }
        
        correlatedNoise /= totalWeight;
        
        // Случайная компонента + память
        double randomComponent = _normalGenerator.Next() * _localVolatility;
        double memoryComponent = correlatedNoise * 0.3;
        
        // Очень медленный дрейф к локальному среднему
        double meanReversion = (_localMean - _currentPrice) / _currentPrice * 0.01;
        
        return memoryComponent + randomComponent + meanReversion;
    }
    
    public (double[] prices, double[] localMean, double[] localVolatility) GenerateSeriesWithStats(int count)
    {
        var prices = new double[count];
        var means = new double[count];
        var volatilities = new double[count];
        
        for (int i = 0; i < count; i++)
        {
            prices[i] = NextPrice();
            means[i] = _localMean;
            volatilities[i] = _localVolatility;
        }
        
        return (prices, means, volatilities);
    }
}

4. Генератор с режимами рынка

csharp
public class RegimeSwitchingPriceGenerator
{
    public enum MarketRegime
    {
        Trending,       // Трендовый режим
        MeanReverting,  // Флэтовый режим  
        Volatile,       // Волатильный режим
        Calm            // Спокойный режим
    }
    
    private readonly NormalDistributionGenerator _normalGenerator;
    private double _currentPrice;
    private MarketRegime _currentRegime;
    private int _regimeDuration;
    private int _timeInRegime;
    
    public RegimeSwitchingPriceGenerator(double initialPrice, int? seed = null)
    {
        _normalGenerator = new NormalDistributionGenerator(seed: seed);
        _currentPrice = initialPrice;
        _currentRegime = MarketRegime.Calm;
        _regimeDuration = 200 + (int)(_normalGenerator.Next() * 300);
        _timeInRegime = 0;
    }
    
    public (double price, MarketRegime regime) NextPrice()
    {
        _timeInRegime++;
        
        // Смена режима
        if (_timeInRegime > _regimeDuration)
        {
            SwitchRegime();
        }
        
        double returns = GenerateReturnsByRegime();
        _currentPrice *= Math.Exp(returns);
        
        return (_currentPrice, _currentRegime);
    }
    
    private void SwitchRegime()
    {
        var regimes = Enum.GetValues<MarketRegime>();
        _currentRegime = regimes[_normalGenerator.Next() > 0 ? 2 : 0]; // Упрощенная логика
        _regimeDuration = 100 + (int)(Math.Abs(_normalGenerator.Next()) * 400);
        _timeInRegime = 0;
    }
    
    private double GenerateReturnsByRegime()
    {
        return _currentRegime switch
        {
            MarketRegime.Trending => _normalGenerator.Next() * 0.01 + 0.001, // Вверх + шум
            MarketRegime.MeanReverting => _normalGenerator.Next() * 0.005,   // Маленькие колебания
            MarketRegime.Volatile => _normalGenerator.Next() * 0.03,         // Большие колебания
            MarketRegime.Calm => _normalGenerator.Next() * 0.002,           // Очень маленькие
            _ => _normalGenerator.Next() * 0.01
        };
    }
    
    public (double[] prices, MarketRegime[] regimes) GenerateSeriesWithRegimes(int count)
    {
        var prices = new double[count];
        var regimes = new MarketRegime[count];
        
        for (int i = 0; i < count; i++)
        {
            (prices[i], regimes[i]) = NextPrice();
        }
        
        return (prices, regimes);
    }
}

5. Пример использования и визуализация

csharp
class Program
{
    static void Main()
    {
        // Тестируем разные генераторы
        Console.WriteLine("ГЕНЕРАЦИЯ РЕАЛИСТИЧНЫХ БИРЖЕВЫХ ДАННЫХ");
        Console.WriteLine("=====================================\n");
        
        // 1. Базовая модель
        var basicGenerator = new RealisticStockPriceGenerator(100, seed: 42);
        var basicPrices = basicGenerator.GeneratePriceSeries(500);
        
        // 2. Модель с каналами
        var channelGenerator = new ChannelBasedPriceGenerator(100, seed: 42);
        var (channelPrices, centers, widths) = channelGenerator.GenerateSeriesWithChannels(500);
        
        // 3. Продвинутая модель
        var advancedGenerator = new AdvancedPriceGenerator(100, seed: 42);
        var (advancedPrices, localMeans, localVolatilities) = advancedGenerator.GenerateSeriesWithStats(500);
        
        // 4. Модель с режимами
        var regimeGenerator = new RegimeSwitchingPriceGenerator(100, seed: 42);
        var (regimePrices, regimes) = regimeGenerator.GenerateSeriesWithRegimes(500);
        
        // Анализ результатов
        AnalyzeSeries("Базовая модель", basicPrices);
        AnalyzeSeries("Модель с каналами", channelPrices);
        AnalyzeSeries("Продвинутая модель", advancedPrices);
        AnalyzeSeries("Модель с режимами", regimePrices);
        
        // Визуализация (псевдографика)
        VisualizePrices(advancedPrices, "Продвинутая модель");
    }
    
    static void AnalyzeSeries(string name, double[] prices)
    {
        double returnsVolatility = CalculateReturnsVolatility(prices);
        double autocorrelation = CalculateAutocorrelation(prices, lag: 1);
        double hurstExponent = EstimateHurstExponent(prices);
        
        Console.WriteLine($"{name}:");
        Console.WriteLine($"  Волатильность: {returnsVolatility:P3}");
        Console.WriteLine($"  Автокорреляция: {autocorrelation:F4}");
        Console.WriteLine($"  Экспонента Хёрста: {hurstExponent:F4}");
        Console.WriteLine($"  Диапазон: {prices.Min():F2} - {prices.Max():F2}");
        Console.WriteLine();
    }
    
    static double CalculateReturnsVolatility(double[] prices)
    {
        var returns = new double[prices.Length - 1];
        for (int i = 1; i < prices.Length; i++)
        {
            returns[i - 1] = Math.Log(prices[i] / prices[i - 1]);
        }
        double mean = returns.Average();
        double variance = returns.Average(r => Math.Pow(r - mean, 2));
        return Math.Sqrt(variance);
    }
    
    static double CalculateAutocorrelation(double[] prices, int lag)
    {
        if (lag >= prices.Length - 1) return 0;
        
        var returns = new double[prices.Length - 1];
        for (int i = 1; i < prices.Length; i++)
        {
            returns[i - 1] = Math.Log(prices[i] / prices[i - 1]);
        }
        
        double mean = returns.Average();
        double variance = returns.Average(r => Math.Pow(r - mean, 2));
        
        if (variance == 0) return 0;
        
        double covariance = 0;
        for (int i = 0; i < returns.Length - lag; i++)
        {
            covariance += (returns[i] - mean) * (returns[i + lag] - mean);
        }
        covariance /= (returns.Length - lag);
        
        return covariance / variance;
    }
    
    static double EstimateHurstExponent(double[] prices)
    {
        // Упрощенная оценка экспоненты Хёрста
        var returns = prices.Select((p, i) => i > 0 ? Math.Log(p / prices[i - 1]) : 0).Skip(1).ToArray();
        return 0.6 + CalculateAutocorrelation(prices, 1) * 0.4; // Эвристика
    }
    
    static void VisualizePrices(double[] prices, string title)
    {
        Console.WriteLine($"{title}:");
        Console.WriteLine(new string('-', 60));
        
        double min = prices.Min();
        double max = prices.Max();
        int height = 20;
        
        for (int y = height - 1; y >= 0; y--)
        {
            double priceLevel = min + (max - min) * y / height;
            Console.Write($"{priceLevel,8:F1} | ");
            
            for (int x = 0; x < Math.Min(50, prices.Length); x++)
            {
                if (Math.Abs(prices[x] - priceLevel) < (max - min) / height * 0.5)
                    Console.Write("●");
                else
                    Console.Write(" ");
            }
            Console.WriteLine();
        }
        Console.WriteLine(new string('-', 60));
    }
}

Ключевые особенности:

  1. Локальность - цены находятся в определенных диапазонах

  2. Память - текущее значение зависит от предыдущих

  3. Кластеризация волатильности - периоды спокойствия и активности

  4. Режимы рынка - трендовые и флэтовые периоды

  5. Поддержка/сопротивление - отскоки от границ каналов

Теперь генерация будет похожа на реальные котировки! 📈

Запустил Ваши Генераторы ГЕНЕРАЦИЯ РЕАЛИСТИЧНЫХ БИРЖЕВЫХ ДАННЫХ ===================================== Базовая модель: Волатильность: 7,540 % Автокорреляция: 0,0144 Экспонента Хёрста: 0,6058 Диапазон: 16,99 - 260,61 Модель с каналами: Волатильность: 1,941 % Автокорреляция: 0,8994 Экспонента Хёрста: 0,9598 Диапазон: 0,00 - 99,56 Продвинутая модель: Волатильность: не число Автокорреляция: не число Экспонента Хёрста: не число Диапазон: не число - не число Модель с режимами: Волатильность: 1,773 % Автокорреляция: -0,1775 Экспонента Хёрста: 0,5290 Диапазон: 68,08 - 103,73 Продвинутая модель: ------------------------------------------------------------ не число | не число | не число | не число | не число | ------------------------------------------------------------ Продвинутая модель пока не взлетела Есть какая-то ошибка

Нашел проблему! В AdvancedPriceGenerator есть несколько ошибок. Давайте исправим:

Исправленная версия AdvancedPriceGenerator

csharp
public class AdvancedPriceGenerator
{
    private readonly NormalDistributionGenerator _normalGenerator;
    private readonly List<double> _priceHistory;
    private readonly int _memorySize;
    private double _currentPrice;
    private double _localMean;
    private double _localVolatility;

    public AdvancedPriceGenerator(double initialPrice, int memorySize = 50, int? seed = null)
    {
        _normalGenerator = new NormalDistributionGenerator(seed: seed);
        _priceHistory = new List<double> { initialPrice };
        _memorySize = memorySize;
        _currentPrice = initialPrice;
        _localMean = initialPrice;
        _localVolatility = initialPrice * 0.02;
    }

    public double NextPrice()
    {
        // Обновляем локальную статистику ДО генерации нового значения
        UpdateLocalStatistics();

        // Генерируем возврат, зависящий от предыдущих значений
        double returns = GenerateCorrelatedReturns();

        // Применяем к цене (защита от отрицательных цен)
        _currentPrice = Math.Max(0.01, _currentPrice * Math.Exp(returns));
        _priceHistory.Add(_currentPrice);

        // Поддерживаем размер истории
        if (_priceHistory.Count > _memorySize)
            _priceHistory.RemoveAt(0);

        return _currentPrice;
    }

    private void UpdateLocalStatistics()
    {
        if (_priceHistory.Count < 5) 
        {
            // На начальном этапе используем разумные значения по умолчанию
            _localMean = _priceHistory.Average();
            _localVolatility = 0.02;
            return;
        }

        try
        {
            // Локальное среднее (скользящее среднее)
            _localMean = _priceHistory.Average();

            // Локальная волатильность (на основе недавних данных)
            var recentPrices = _priceHistory.TakeLast(Math.Min(20, _priceHistory.Count)).ToArray();
            
            // Вычисляем логарифмические возвраты для волатильности
            var returns = new List<double>();
            for (int i = 1; i < recentPrices.Length; i++)
            {
                if (recentPrices[i-1] > 0 && recentPrices[i] > 0)
                {
                    double ret = Math.Log(recentPrices[i] / recentPrices[i-1]);
                    returns.Add(ret);
                }
            }
            
            if (returns.Count > 1)
            {
                double meanReturn = returns.Average();
                double variance = returns.Sum(r => Math.Pow(r - meanReturn, 2)) / (returns.Count - 1);
                _localVolatility = Math.Sqrt(variance);
            }
            else
            {
                _localVolatility = 0.02; // значение по умолчанию
            }

            // Ограничиваем волатильность разумными пределами
            _localVolatility = Math.Max(0.001, Math.Min(0.5, _localVolatility));
        }
        catch (Exception ex)
        {
            // Запасные значения в случае ошибки
            Console.WriteLine($"Ошибка в UpdateLocalStatistics: {ex.Message}");
            _localMean = _priceHistory.Average();
            _localVolatility = 0.02;
        }
    }

    private double GenerateCorrelatedReturns()
    {
        // Если история слишком короткая, используем простой случайный возврат
        if (_priceHistory.Count < 3)
        {
            return _normalGenerator.Next() * _localVolatility;
        }

        double correlatedNoise = 0;
        double totalWeight = 0;

        try
        {
            for (int i = 1; i <= Math.Min(5, _priceHistory.Count - 1); i++)
            {
                int idx1 = _priceHistory.Count - i - 1;
                int idx2 = _priceHistory.Count - i;
                
                if (idx1 >= 0 && idx2 >= 0 && 
                    _priceHistory[idx1] > 0 && _priceHistory[idx2] > 0)
                {
                    double weight = Math.Exp(-i * 0.5); // Экспоненциально убывающие веса
                    double pastReturn = Math.Log(_priceHistory[idx2] / _priceHistory[idx1]);
                    correlatedNoise += pastReturn * weight;
                    totalWeight += weight;
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Ошибка в вычислении корреляции: {ex.Message}");
            correlatedNoise = 0;
            totalWeight = 1;
        }

        if (totalWeight > 0)
            correlatedNoise /= totalWeight;

        // Случайная компонента + память
        double randomComponent = _normalGenerator.Next() * _localVolatility;
        double memoryComponent = correlatedNoise * 0.3;

        // Очень медленный дрейф к локальному среднему (если цена положительная)
        double meanReversion = 0;
        if (_currentPrice > 0)
        {
            meanReversion = (_localMean - _currentPrice) / _currentPrice * 0.005; // уменьшил коэффициент
        }

        return memoryComponent + randomComponent + meanReversion;
    }

    public (double[] prices, double[] localMean, double[] localVolatility) GenerateSeriesWithStats(int count)
    {
        var prices = new double[count];
        var means = new double[count];
        var volatilities = new double[count];

        for (int i = 0; i < count; i++)
        {
            prices[i] = NextPrice();
            means[i] = _localMean;
            volatilities[i] = _localVolatility;
        }

        return (prices, means, volatilities);
    }
}

Также исправим NormalDistributionGenerator (на всякий случай)

csharp
public class NormalDistributionGenerator
{
    private readonly Random _random;
    private readonly double _mean;
    private readonly double _stdDev;
    private bool _hasSpareValue;
    private double _spareValue;

    public NormalDistributionGenerator(double mean = 0, double stdDev = 1, int? seed = null)
    {
        _mean = mean;
        _stdDev = stdDev;
        _random = seed.HasValue ? new Random(seed.Value) : new Random();
        _hasSpareValue = false;
        _spareValue = 0;
    }

    public double Next()
    {
        if (_hasSpareValue)
        {
            _hasSpareValue = false;
            return _spareValue * _stdDev + _mean;
        }

        double u, v, s;
        do
        {
            u = _random.NextDouble() * 2 - 1;
            v = _random.NextDouble() * 2 - 1;
            s = u * u + v * v;
        } while (s >= 1 || s == 0);

        s = Math.Sqrt(-2.0 * Math.Log(s) / s);
        _spareValue = v * s;
        _hasSpareValue = true;

        double result = u * s * _stdDev + _mean;
        
        // Защита от экстремальных значений
        if (double.IsInfinity(result) || double.IsNaN(result))
            return _mean;
            
        return result;
    }

    public double[] GenerateSample(int size)
    {
        var sample = new double[size];
        for (int i = 0; i < size; i++)
        {
            sample[i] = Next();
        }
        return sample;
    }
}

Улучшенная функция анализа

csharp
static void AnalyzeSeries(string name, double[] prices)
{
    try
    {
        // Проверяем на валидность данных
        if (prices == null || prices.Length == 0 || prices.Any(p => double.IsNaN(p) || double.IsInfinity(p)))
        {
            Console.WriteLine($"{name}: НЕВАЛИДНЫЕ ДАННЫЕ");
            return;
        }

        double returnsVolatility = CalculateReturnsVolatility(prices);
        double autocorrelation = CalculateAutocorrelation(prices, lag: 1);
        double hurstExponent = EstimateHurstExponent(prices);
        
        Console.WriteLine($"{name}:");
        Console.WriteLine($"  Волатильность: {returnsVolatility:P3}");
        Console.WriteLine($"  Автокорреляция: {autocorrelation:F4}");
        Console.WriteLine($"  Экспонента Хёрста: {hurstExponent:F4}");
        Console.WriteLine($"  Диапазон: {prices.Min():F2} - {prices.Max():F2}");
        Console.WriteLine($"  Последние цены: {string.Join(", ", prices.TakeLast(3).Select(p => p.ToString("F2")))}");
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"{name}: ОШИБКА АНАЛИЗА - {ex.Message}");
    }
}

static double CalculateReturnsVolatility(double[] prices)
{
    try
    {
        var returns = new List<double>();
        for (int i = 1; i < prices.Length; i++)
        {
            if (prices[i-1] > 0 && prices[i] > 0)
            {
                double ret = Math.Log(prices[i] / prices[i-1]);
                if (!double.IsInfinity(ret) && !double.IsNaN(ret))
                    returns.Add(ret);
            }
        }
        
        if (returns.Count == 0) return 0;
        
        double mean = returns.Average();
        double variance = returns.Sum(r => Math.Pow(r - mean, 2)) / (returns.Count - 1);
        return Math.Sqrt(variance);
    }
    catch
    {
        return 0;
    }
}

Основные исправления:

  1. Защита от деления на ноль - проверка положительных цен

  2. Обработка исключений - try-catch вокруг опасных операций

  3. Валидация данных - проверка на NaN и Infinity

  4. Корректное вычисление волатильности - через логарифмические возвраты

  5. Запасные значения - на случай ошибок в вычислениях

Теперь продвинутая модель должна работать корректно! Запустите снова и посмотрите на результаты. 🚀

Комментариев нет:

Отправить комментарий