Files
OxyPlot/Source/Examples/ExampleLibrary/Series/FinancialSeries/OhlcvItemGenerator.cs
2023-09-02 09:24:59 +02:00

211 lines
7.6 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="OhlcvItemGenerator.cs" company="OxyPlot">
// Copyright (c) 2014 OxyPlot contributors
// </copyright>
// <summary>
// Creates realistic OHLCV items.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ExampleLibrary
{
using System;
using System.Collections.Generic;
using OxyPlot.Axes;
using OxyPlot.Series;
/// <summary>
/// Creates realistic OHLCV items.
/// </summary>
public static class OhlcvItemGenerator
{
/// <summary>
/// The random number generator.
/// </summary>
private static readonly Random Rand = new Random();
/// <summary>
/// Creates bars governed by a MR process.
/// </summary>
/// <param name="n">N.</param>
/// <param name="x0">X0.</param>
/// <param name="v0">V0.</param>
/// <param name="csigma">Csigma.</param>
/// <param name="esigma">Esigma.</param>
/// <param name="kappa">Kappa.</param>
/// <returns>
/// The process.
/// </returns>
public static IEnumerable<OhlcvItem> MRProcess(
int n,
double x0 = 100.0,
double v0 = 500,
double csigma = 0.50,
double esigma = 0.75,
double kappa = 0.01)
{
double x = x0;
var baseT = DateTime.UtcNow;
for (int ti = 0; ti < n; ti++)
{
var dx_c = -kappa * (x - x0) + RandomNormal(0, csigma);
var dx_1 = -kappa * (x - x0) + RandomNormal(0, esigma);
var dx_2 = -kappa * (x - x0) + RandomNormal(0, esigma);
var open = x;
var close = x = x + dx_c;
var low = Min(open, close, open + dx_1, open + dx_2);
var high = Max(open, close, open + dx_1, open + dx_2);
var dp = close - open;
var v = v0 * Math.Exp(Math.Abs(dp) / csigma);
var dir = (dp < 0) ?
-Math.Min(-dp / esigma, 1.0) :
Math.Min(dp / esigma, 1.0);
var skew = (dir + 1) / 2.0;
var buyvol = skew * v;
var sellvol = (1 - skew) * v;
var nowT = baseT.AddSeconds(ti);
var t = DateTimeAxis.ToDouble(nowT);
yield return new OhlcvItem(t, open, high, low, close, buyvol, sellvol);
}
}
/// <summary>
/// Finds the minimum of the specified a, b, c and d.
/// </summary>
/// <param name="a">A.</param>
/// <param name="b">B.</param>
/// <param name="c">C.</param>
/// <param name="d">D.</param>
/// <returns>The minimum.</returns>
private static double Min(double a, double b, double c, double d)
{
return Math.Min(a, Math.Min(b, Math.Min(c, d)));
}
/// <summary>
/// Finds the maximum of the specified a, b, c and d.
/// </summary>
/// <param name="a">A.</param>
/// <param name="b">B.</param>
/// <param name="c">C.</param>
/// <param name="d">D.</param>
/// <returns>The maximum.</returns>
private static double Max(double a, double b, double c, double d)
{
return Math.Max(a, Math.Max(b, Math.Max(c, d)));
}
/// <summary>
/// Gets random normal
/// </summary>
/// <param name="mu">Mu.</param>
/// <param name="sigma">Sigma.</param>
/// <returns></returns>
private static double RandomNormal(double mu, double sigma)
{
return InverseCumNormal(Rand.NextDouble(), mu, sigma);
}
/// <summary>
/// Fast approximation for inverse cum normal
/// </summary>
/// <param name="p">probability</param>
/// <param name="mu">Mean</param>
/// <param name="sigma">std dev</param>
private static double InverseCumNormal(double p, double mu, double sigma)
{
const double A1 = -3.969683028665376e+01;
const double A2 = 2.209460984245205e+02;
const double A3 = -2.759285104469687e+02;
const double A4 = 1.383577518672690e+02;
const double A5 = -3.066479806614716e+01;
const double A6 = 2.506628277459239e+00;
const double B1 = -5.447609879822406e+01;
const double B2 = 1.615858368580409e+02;
const double B3 = -1.556989798598866e+02;
const double B4 = 6.680131188771972e+01;
const double B5 = -1.328068155288572e+01;
const double C1 = -7.784894002430293e-03;
const double C2 = -3.223964580411365e-01;
const double C3 = -2.400758277161838e+00;
const double C4 = -2.549732539343734e+00;
const double C5 = 4.374664141464968e+00;
const double C6 = 2.938163982698783e+00;
const double D1 = 7.784695709041462e-03;
const double D2 = 3.224671290700398e-01;
const double D3 = 2.445134137142996e+00;
const double D4 = 3.754408661907416e+00;
const double Xlow = 0.02425;
const double Xhigh = 1.0 - Xlow;
double z, r;
if (p < Xlow)
{
// Rational approximation for the lower region 0<x<u_low
z = Math.Sqrt(-2.0 * Math.Log(p));
z = (((((C1 * z + C2) * z + C3) * z + C4) * z + C5) * z + C6) /
((((D1 * z + D2) * z + D3) * z + D4) * z + 1.0);
}
else if (p <= Xhigh)
{
// Rational approximation for the central region u_low<=x<=u_high
z = p - 0.5;
r = z * z;
z = (((((A1 * r + A2) * r + A3) * r + A4) * r + A5) * r + A6) * z /
(((((B1 * r + B2) * r + B3) * r + B4) * r + B5) * r + 1.0);
}
else
{
// Rational approximation for the upper region u_high<x<1
z = Math.Sqrt(-2.0 * Math.Log(1.0 - p));
z = -(((((C1 * z + C2) * z + C3) * z + C4) * z + C5) * z + C6) /
((((D1 * z + D2) * z + D3) * z + D4) * z + 1.0);
}
// error (f_(z) - x) divided by the cumulative's derivative
r = (CumN0(z) - p) * Math.Sqrt(2.0) * Math.Sqrt(Math.PI) * Math.Exp(0.5 * z * z);
// Halley's method
z -= r / (1 + (0.5 * z * r));
return mu + (z * sigma);
}
/// <summary>
/// Cumulative for a N(0,1) distribution
/// </summary>
/// <returns>The n0.</returns>
/// <param name="x">The x coordinate.</param>
private static double CumN0(double x)
{
const double B1 = 0.319381530;
const double B2 = -0.356563782;
const double B3 = 1.781477937;
const double B4 = -1.821255978;
const double B5 = 1.330274429;
const double P = 0.2316419;
const double C = 0.39894228;
if (x >= 0.0)
{
double t = 1.0 / (1.0 + (P * x));
return (1.0 - C * Math.Exp(-x * x / 2.0) * t * (t * (t * (t * (t * B5 + B4) + B3) + B2) + B1));
}
else
{
double t = 1.0 / (1.0 - P * x);
return (C * Math.Exp(-x * x / 2.0) * t * (t * (t * (t * (t * B5 + B4) + B3) + B2) + B1));
}
}
}
}