25.10.14, DeepSeek, Generators, Stat, Test
Создадим полную систему для генерации и тестирования нормального распределения.
1. Генератор нормального распределения (Box-Muller)
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. Статистические функции для тестов
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. Тест Колмогорова-Смирнова
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. Тест хи-квадрат
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. Тест Шапиро-Уилка (упрощенная версия)
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. Полная система тестирования
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. Пример использования
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;
}
}Особенности реализации:
Box-Muller алгоритм - эффективная генерация нормальных чисел
Три статистических теста - комплексная проверка нормальности
Обработка граничных случаев - проверка размеров выборок
Визуализация результатов - понятные выводы
Сравнение с ненормальным распределением - демонстрация работы тестов
Это полная система для генерации и тестирования нормальных распределений! 🎯
Отлично понимаю вашу проблему! Вы описываете нестационарный процесс с локальной автокорреляцией, а обычное нормальное распределение дает стационарный белый шум. Давайте создадим генератор, который имитирует реальные котировки.
1. Модель с дрейфом и локальной волатильностью
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. Модель с каналами и поддержкой/сопротивлением
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. Продвинутая модель с памятью и кластеризацией
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. Генератор с режимами рынка
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. Пример использования и визуализация
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));
}
}Ключевые особенности:
Локальность - цены находятся в определенных диапазонах
Память - текущее значение зависит от предыдущих
Кластеризация волатильности - периоды спокойствия и активности
Режимы рынка - трендовые и флэтовые периоды
Поддержка/сопротивление - отскоки от границ каналов
Теперь генерация будет похожа на реальные котировки! 📈
Нашел проблему! В AdvancedPriceGenerator есть несколько ошибок. Давайте исправим:
Исправленная версия AdvancedPriceGenerator
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 (на всякий случай)
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;
}
}Улучшенная функция анализа
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;
}
}Основные исправления:
Защита от деления на ноль - проверка положительных цен
Обработка исключений - try-catch вокруг опасных операций
Валидация данных - проверка на NaN и Infinity
Корректное вычисление волатильности - через логарифмические возвраты
Запасные значения - на случай ошибок в вычислениях
Теперь продвинутая модель должна работать корректно! Запустите снова и посмотрите на результаты. 🚀
Комментариев нет:
Отправить комментарий