Применение Фурье преобразования в торговле

dev
vlad zverzhkhovskiy 2025-09-16 16:37:47 +03:00
parent d0ec2af488
commit 2b9f7cf332
16 changed files with 647 additions and 227 deletions

View File

@ -11,6 +11,7 @@
ShortOpen = 16,
UptrendEnd = 32,
UptrendStart = 64,
HorisontTrend = 128,
DowntrendEnd = 128,
DowntrendStart = 256,
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KLHZ.Trader.Core.Math.Declisions.Dtos.FFT.Enums
{
public enum ValueAmplitudePosition
{
None = 0,
Middle = 1,
UpperThen20Decil = 10,
LowerThenMediana = -50,
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KLHZ.Trader.Core.Math.Declisions.Dtos.FFT
{
public class FFTAnalyzeResult
{
public required string Key { get; init; }
public Harmonic[] Harmonics { get; init; } = [];
public decimal Max { get; init; }
public decimal Min { get; init; }
public decimal Mediana { get; init; }
public decimal Upper20Decil { get; init; }
public decimal Lower20Decil { get; init; }
public DateTime LastTime { get; init; }
public DateTime StartTime { get; init; }
public bool IsEmpty => this == Empty;
public static FFTAnalyzeResult Empty = new() { Key = "empty" };
}
}

View File

@ -0,0 +1,11 @@
namespace KLHZ.Trader.Core.Math.Declisions.Dtos.FFT
{
public class Harmonic
{
public TimeSpan Period { get; init; }
public float Magnitude { get; init; }
public float Real { get; init; }
public float Imaginary { get; init; }
public float Phase { get; init; }
}
}

View File

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KLHZ.Trader.Core.Math.Declisions.Dtos
{
public class Harmonic
{
public TimeSpan Period { get; set; }
public float Magnitude { get; set; }
public float Real { get; set; }
public float Imaginary { get; set; }
public float Phase { get; set; }
}
}

View File

@ -9,8 +9,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
{
public class PriceHistoryCacheUnit2 : IPriceHistoryCacheUnit
{
public const int CacheMaxLength = 1500;
private const int _arrayMaxLength = 5000;
public const int CacheMaxLength = 30000;
private const int _arrayMaxLength = 60000;
public string Figi { get; init; }

View File

@ -1,4 +1,5 @@
using KLHZ.Trader.Core.Math.Declisions.Dtos;
using KLHZ.Trader.Core.Math.Declisions.Dtos.FFT;
using KLHZ.Trader.Core.Math.Declisions.Dtos.FFT.Enums;
using MathNet.Numerics;
using MathNet.Numerics.IntegralTransforms;
@ -6,28 +7,102 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
public static class FFT
{
public static void Test()
public static ValueAmplitudePosition Check(FFTAnalyzeResult fftData, DateTime timestamp)
{
var da = new List<float>();
for (int i = 0; i < 1000; i++)
var value = (decimal)CalcAmplitude(fftData.Harmonics, fftData.StartTime, timestamp);
var value2 = (decimal)CalcExtremum(fftData.Harmonics, fftData.StartTime, timestamp);
if (value > fftData.Upper20Decil)
{
da.Add((float)System.Math.Sin(0.01 * i) + (float)System.Math.Cos(0.01 * i));
return ValueAmplitudePosition.UpperThen20Decil;
}
var start = da.ToArray();
var arrv = da.Select(d => new Complex32(d, 0)).ToArray();
Fourier.Forward(arrv);
Fourier.Inverse(arrv);
var res = arrv.Select(a => a.Real).ToArray();
for (int i = 0; i < 1000; i++)
else if (value < fftData.Mediana)
{
var d = res[i] - start[i];
return ValueAmplitudePosition.LowerThenMediana;
}
else
{
return ValueAmplitudePosition.Middle;
}
}
public static TimeSpan GetMainHarmonictPeriod(double[] data, TimeSpan period)
public static FFTAnalyzeResult Analyze(DateTime[] timestamps, decimal[] values, string key, TimeSpan minPeriod, TimeSpan maxPeriod)
{
var harmonics = GetHarmonics(values, timestamps[timestamps.Length - 1] - timestamps[0], minPeriod, maxPeriod);
var newValues = new decimal[timestamps.Length];
var newValues2 = new decimal[timestamps.Length];
var startTime = timestamps[0];
for (int i = 0; i < timestamps.Length; i++)
{
newValues[i] = (decimal)CalcAmplitude(harmonics, startTime, timestamps[i]);
newValues2[i] = (decimal)CalcExtremum(harmonics, startTime, timestamps[i]);
}
newValues = newValues.Order().ToArray();
var ma = newValues2.Max();
var mi = newValues2.Min();
return new FFTAnalyzeResult()
{
Key = key,
Harmonics = harmonics,
LastTime = timestamps[timestamps.Length - 1],
StartTime = startTime,
Mediana = newValues[newValues.Length / 2],
Upper20Decil = newValues[(int)(newValues.Length * 0.8)],
Lower20Decil = newValues[(int)(newValues.Length * 0.2)],
Max = newValues.Max(),
Min = newValues.Min(),
};
}
public static Harmonic[] GetHarmonics(decimal[] data, TimeSpan period, TimeSpan minPeriod, TimeSpan maxPeriod)
{
var arrv = data.Select(d => new Complex32((float)d, 0)).ToArray();
Fourier.Forward(arrv);
var res = new List<Harmonic>();
for (int i = 1; i < arrv.Length; i++)
{
var per = CaclHarmonycPeriod(period, data.Length, i);
if (per >= minPeriod && per <= maxPeriod)
{
res.Add(new Harmonic()
{
Imaginary = arrv[i].Imaginary,
Real = arrv[i].Real,
Magnitude = arrv[i].Magnitude,
Period = per,
Phase = arrv[i].Phase,
});
}
}
return res.ToArray();
}
public static double CalcAmplitude(Harmonic[] harmonics, DateTime startTime, DateTime currentTime)
{
var sum = 0d;
var timeSpan = currentTime - startTime;
for (int i = 0; i < harmonics.Length; i++)
{
var value = harmonics[i].Magnitude * System.Math.Cos(2 * System.Math.PI * timeSpan.TotalSeconds / harmonics[i].Period.TotalSeconds + harmonics[i].Phase);
sum += value;
}
return sum;
}
public static double CalcExtremum(Harmonic[] harmonics, DateTime startTime, DateTime currentTime)
{
var sum = 0d;
var timeSpan = currentTime - startTime;
for (int i = 0; i < harmonics.Length; i++)
{
var value = - harmonics[i].Magnitude * System.Math.Sin(2 * System.Math.PI * timeSpan.TotalSeconds / harmonics[i].Period.TotalSeconds + harmonics[i].Phase);
sum += value;
}
return sum;
}
internal static TimeSpan GetMainHarmonictPeriod(double[] data, TimeSpan period)
{
var arrv = data.Select(d => new Complex32((float)d, 0)).ToArray();
Fourier.Forward(arrv);
@ -38,7 +113,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
var max = 0f;
var kmax = 1;
var sum = 0f;
for (int i=0;i< arrv.Length / 2; i++)
for (int i = 0; i < arrv.Length / 2; i++)
{
if (i == 0)
{
@ -70,64 +145,30 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
return CaclHarmonycPeriod(period, data.Length, kmax);
}
public static Harmonic[] GetHarmonics(double[] data, TimeSpan period, int count)
{
var arrv = data.Select(d => new Complex32((float)d, 0)).ToArray();
Fourier.Forward(arrv);
var res = new List<Harmonic>();
for (int i = 1; i < count; i++)
{
res.Add(new Harmonic()
{
Imaginary = arrv[i].Imaginary,
Real = arrv[i].Real,
Magnitude = arrv[i].Magnitude,
Period = CaclHarmonycPeriod(period, data.Length, i),
Phase = arrv[i].Phase,
});
}
return res.ToArray();
}
public static double Calc(Harmonic[] harmonics, DateTime startTime, DateTime endTime, DateTime currentTime)
{
var sum = 0d;
var timeSpan = currentTime - startTime;
for (int i = 0; i < harmonics.Length; i++)
{
var currentPhase = System.Math.PI * 2 * timeSpan.TotalSeconds * (endTime - startTime).TotalSeconds / harmonics[i].Period.TotalSeconds / harmonics[i].Period.TotalSeconds + harmonics[i].Phase;
var value = harmonics[i].Real * System.Math.Cos(currentPhase) + harmonics[i].Imaginary * System.Math.Sign(currentPhase);
sum += value;
}
return sum;
}
public static TimeSpan CaclHarmonycPeriod(TimeSpan signalLength, int signalLengthItems, int harmonyNumber)
internal static TimeSpan CaclHarmonycPeriod(TimeSpan signalLength, int signalLengthItems, int harmonyNumber)
{
var fdiscretisation = signalLengthItems / signalLength.TotalSeconds;
var fharm = harmonyNumber * fdiscretisation / signalLengthItems;
return TimeSpan.FromSeconds(1 / fharm);
}
public static double CalcCurrentPhase(Complex32[] spectr, int harmonyNumber)
internal static double CalcCurrentPhase(Complex32[] spectr, int harmonyNumber)
{
var item = spectr[harmonyNumber];
return System.Math.Atan(item.Imaginary / item.Real);
}
public static (double min, double max) CalcPhaseRangeFoxMax(double aSin, double aCos, double initPhase, double level)
internal static (double min, double max) CalcPhaseRangeFoxMax(double aSin, double aCos, double initPhase, double level)
{
return CalcPhaseRange(aSin, aCos,initPhase, level, CheckMaxValue);
return CalcPhaseRange(aSin, aCos, initPhase, level, CheckMaxValue);
}
public static (double min, double max) CalcPhaseRangeFoxMin(double aSin, double aCos, double initPhase, double level)
internal static (double min, double max) CalcPhaseRangeFoxMin(double aSin, double aCos, double initPhase, double level)
{
return CalcPhaseRange(aSin, aCos, initPhase, level, CheckMinValue);
}
internal static (double min, double max) CalcPhaseRange(double aSin, double aCos, double initPhase, double level, Func<double, double, double, double,bool> comparer)
internal static (double min, double max) CalcPhaseRange(double aSin, double aCos, double initPhase, double level, Func<double, double, double, double, bool> comparer)
{
var x = new List<double>();
var xIndexes = new List<int>();
@ -164,7 +205,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
var maxPhase = 0d;
for (int i = 0; i < y.Count; i++)
{
if (comparer(y[i],min,max,level))
if (comparer(y[i], min, max, level))
{
if (start < 0)
{

View File

@ -8,7 +8,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
/// </summary>
public static class LocalTrends
{
internal static bool TryGetLocalTrends(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan lastPeriod,
public static bool TryGetLocalTrends(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan lastPeriod,
double meanfullDiff, out TradingEvent res)
{
res = TradingEvent.None;
@ -51,32 +51,44 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
}
}
var line1 = Fit.Line(x1.ToArray(), y1.ToArray());
var line2 = Fit.Line(x2.ToArray(), y2.ToArray());
foreach (var x in x1)
if (x1.Count>1 && x2.Count > 1)
{
y1_approximated.Add(line1.A + x * line1.B);
var line1 = Fit.Line(x1.ToArray(), y1.ToArray());
var line2 = Fit.Line(x2.ToArray(), y2.ToArray());
foreach (var x in x1)
{
y1_approximated.Add(line1.A + x * line1.B);
}
foreach (var x in x2)
{
y2_approximated.Add(line2.A + x * line2.B);
}
var diff1 = y1_approximated[0] - y1_approximated[y1_approximated.Count - 1];
var diff2 = y2_approximated[0] - y2_approximated[y2_approximated.Count - 1];
if (diff1 <= -meanfullDiff && diff2 >= meanfullDiff)
{
res |= TradingEvent.UptrendStart;
}
else if (diff1 >= meanfullDiff && diff2 <= 0)
{
res |= TradingEvent.UptrendEnd;
}
else if (diff1 <= -meanfullDiff && diff2 >= 0)
{
res |= TradingEvent.DowntrendEnd;
}
else if (diff1 >= 0 && diff2 <= -meanfullDiff)
{
res |= TradingEvent.DowntrendStart;
}
success = true;
}
foreach (var x in x2)
{
y2_approximated.Add(line2.A + x * line2.B);
}
var diff1 = y1_approximated[0] - y1_approximated[y1_approximated.Count - 1];
var diff2 = y2_approximated[0] - y2_approximated[y2_approximated.Count - 1];
if (diff1 <= -meanfullDiff && diff2 >= meanfullDiff)
{
res |= TradingEvent.UptrendStart;
}
else if (diff1 >= meanfullDiff && diff2 <= 0)
{
res |= TradingEvent.UptrendEnd;
}
success = true;
}
return success;
}
internal static bool TryCalcTrendDiff(DateTime[] times, decimal[] prices, out decimal diff)
public static bool TryCalcTrendDiff(DateTime[] times, decimal[] prices, out decimal diff)
{
diff = 0;
if (times.Length <= 1 || times.Length != prices.Length)

View File

@ -2,9 +2,9 @@
{
public static class SignalProcessing
{
public static (DateTime[], double[]) InterpolateData(DateTime[] timestamps, double[] values, TimeSpan timeStep)
public static (DateTime[] timestamps, decimal[] values) InterpolateData(DateTime[] timestamps, decimal[] values, TimeSpan timeStep)
{
var res = new List<double>();
var res = new List<decimal>();
var res2 = new List<DateTime>();
var startTime = new DateTime(timestamps[0].Year, timestamps[0].Month, timestamps[0].Day, 0, 0, 0, DateTimeKind.Utc);
@ -12,7 +12,7 @@
var totalSteps = System.Math.Ceiling((timestamps[timestamps.Length - 1] - timestamps[0]).TotalSeconds / timeStep.TotalSeconds);
var deltaSeconds = System.Math.Floor(dt.TotalSeconds / timeStep.TotalSeconds);
startTime = startTime.AddSeconds(deltaSeconds * timeStep.TotalSeconds) ;
startTime = startTime.AddSeconds(deltaSeconds * timeStep.TotalSeconds);
var firstBound = startTime;
var secondBound = startTime + timeStep;
@ -20,11 +20,11 @@
for (int i = 0; i < totalSteps; i++)
{
var count = 0;
var sum = 0d;
var sum = 0m;
for (int i1 = bound; i1 < timestamps.Length; i1++)
{
if (timestamps[i1]> firstBound && timestamps[i1] <= secondBound)
if (timestamps[i1] > firstBound && timestamps[i1] <= secondBound)
{
count++;
sum += values[i1];
@ -40,9 +40,9 @@
res.Add(sum / count);
res2.Add(secondBound);
}
else if (bound< timestamps.Length-2)
else if (bound < timestamps.Length - 2)
{
res.Add(res.Last());
res.Add(values[bound]);
res2.Add(secondBound);
}

View File

@ -1,12 +1,82 @@
using KLHZ.Trader.Core.Math.Declisions.Utils;
using MathNet.Numerics;
using MathNet.Numerics.IntegralTransforms;
using System.Security.Cryptography;
namespace KLHZ.Trader.Core.Tests
{
public class FFTTests
{
[Test]
public static void Test1()
{
// Размер сигнала
int N = 1024;
// Генерируем случайный сигнал
var signal = new float[N];
Random random = new Random();
for (int i = 0; i < N; i++)
{
signal[i] = (float)System.Math.Cos(0.01 * i);// (float)random.NextDouble() * 2 - 1;
}
// нормированный случайный сигнал [-1, 1]
// Выполняем прямое преобразование Фурье
Complex32[] fftResult = signal.Select(s => new Complex32(s, 0)).ToArray();
Fourier.Forward(fftResult);
// Выделяем первые 10 гармоник (включая нулевую)
var firstTenHarmonics = fftResult.AsSpan(0, 10).ToArray();
//Копируем первые 11 элементов (нулевая и первые 10)
// Вычисляем амплитуды и фазы гармоник
double[] amplitudes = new double[firstTenHarmonics.Length];
double[] phases = new double[firstTenHarmonics.Length];
for (int k = 0; k < firstTenHarmonics.Length; k++)
{
amplitudes[k] = firstTenHarmonics[k].Magnitude / N;
phases[k] = firstTenHarmonics[k].Phase;
}
// Последний индекс сигнала
int lastPointIndex = N - 1;
// Реконструкция последней точки сигнала
double reconstructedLastPoint = 0;
double reconstructedFirstPoint = 0;
for (int k = 1; k < firstTenHarmonics.Length; k++) // начинаем с первой гармоники
{
reconstructedLastPoint += amplitudes[k] *
System.Math.Cos((2 * System.Math.PI * k * lastPointIndex) / N + phases[k]);
}
for (int k = 1; k < firstTenHarmonics.Length; k++) // начинаем с первой гармоники
{
reconstructedFirstPoint += amplitudes[k] *
System.Math.Cos((2 * System.Math.PI * k * 1) / N + phases[k]);
}
Console.WriteLine($"Реконструированное значение последней точки: {reconstructedLastPoint}");
}
public static Complex32[] InverseFourierManual(Complex32[] spectrum)
{
int N = spectrum.Length;
Complex32[] result = new Complex32[N];
for (int t = 0; t < N; t++)
{
Complex32 sum = Complex32.Zero;
for (int k = 0; k < N; k++)
{
double angle = 2 * System.Math.PI * k * t / N;
sum += spectrum[k] * Complex32.Exp(new Complex32(0, (float)angle));
}
result[t] = sum / N;
}
return result;
}
[Test]
public static void Test()
{
@ -14,32 +84,60 @@ namespace KLHZ.Trader.Core.Tests
var da2 = new List<float>();
var dates = new List<DateTime>();
var dt = DateTime.UtcNow;
for (int i = 0; i < 1000; i++)
var dt1 = dt;
var dt2 = dt.AddSeconds(3600);
var T1 = TimeSpan.FromMinutes(10);
var T2 = TimeSpan.FromMinutes(7);
var T3 = TimeSpan.FromMinutes(30);
while (dt < dt2)
{
dt = dt.AddSeconds(1);
da.Add((float)System.Math.Sin(0.01 * i) + (float)System.Math.Cos(0.02 * i));
var phase = (dt - dt1).TotalSeconds / T1.TotalSeconds * 2 * System.Math.PI;
var phase2 = (dt - dt1).TotalSeconds / T2.TotalSeconds * 2 * System.Math.PI;
var phase3 = (dt - dt1).TotalSeconds / T3.TotalSeconds * 2 * System.Math.PI;
da.Add((float)System.Math.Cos(phase) + (float)System.Math.Sin(phase2) + (float)System.Math.Sin(phase3));
dates.Add(dt);
}
var start = da.ToArray();
var arrv = da.Select(d => new Complex32(d, 0)).ToArray();
Fourier.Forward(arrv);
var harms = FFT.GetHarmonics(da.Select(d => (double)d).ToArray(), dates.Last() - dates.First(), 20);
var harms = FFT.GetHarmonics(da.Select(d => (decimal)d).ToArray(), dates.Last() - dates.First(), TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(70));
foreach(var d in dates)
var damax = da.Max();
var damin = da.Min();
for (int i = 0; i < da.Count; i++)
{
da2.Add((float)FFT.Calc(harms, dates.First(), dates.Last(), d));
da[i] = da[i] / (damax - damin);
}
//var tem = arrv.Select(a => Complex32.Abs(a)).ToArray();
foreach (var d in dates)
{
da2.Add((float)FFT.CalcAmplitude(harms, dates.First(), d));
}
var da2max = da2.Max();
var da2min = da2.Min();
for (int i = 0; i < da2.Count; i++)
{
da2[i] = da2[i] / (da2max - da2min);
}
damax = da.Max();
damin = da.Min();
da2max = da2.Max();
da2min = da2.Min();
//var tem = arrv.Select(a => Complex32.Abs(a)).ToArray();
//var s = tem.Sum();
//Fourier.Inverse(arrv);
//var res = arrv.Select(a => a.Real).ToArray();
//for (int i = 0; i < 1000; i++)
//{
// var d = res[i] - start[i];
//}
var diffs = new List<float>();
for (int i = 0; i < da2.Count; i++)
{
var diff = (da2[i] - da[i]) / (damax - damin);
diffs.Add(diff);
}
}
[Test]
@ -66,20 +164,20 @@ namespace KLHZ.Trader.Core.Tests
public static void CalcPhaseRangeForMin_Sin()
{
var res = FFT.CalcPhaseRangeFoxMin(1, 0, 0, 0.001);
Assert.IsTrue(res.min < 3*System.Math.PI / 2);
Assert.IsTrue(res.min > 3*System.Math.PI / 2 * 0.9);
Assert.IsTrue(res.max > 3*System.Math.PI / 2);
Assert.IsTrue(res.max < 3*System.Math.PI / 2 * 1.1);
Assert.IsTrue(res.min < 3 * System.Math.PI / 2);
Assert.IsTrue(res.min > 3 * System.Math.PI / 2 * 0.9);
Assert.IsTrue(res.max > 3 * System.Math.PI / 2);
Assert.IsTrue(res.max < 3 * System.Math.PI / 2 * 1.1);
}
[Test]
public static void CalcPhaseRangeForMax_MinusSin()
{
var res = FFT.CalcPhaseRangeFoxMax(-1, 0, 0, 0.001);
Assert.IsTrue(res.min < 3*System.Math.PI / 2);
Assert.IsTrue(res.min > 3*System.Math.PI / 2 * 0.9);
Assert.IsTrue(res.max > 3*System.Math.PI / 2);
Assert.IsTrue(res.max < 3*System.Math.PI / 2 * 1.1);
Assert.IsTrue(res.min < 3 * System.Math.PI / 2);
Assert.IsTrue(res.min > 3 * System.Math.PI / 2 * 0.9);
Assert.IsTrue(res.max > 3 * System.Math.PI / 2);
Assert.IsTrue(res.max < 3 * System.Math.PI / 2 * 1.1);
}
[Test]
@ -89,7 +187,7 @@ namespace KLHZ.Trader.Core.Tests
Assert.IsTrue(res.min < System.Math.PI * 2);
Assert.IsTrue(res.min > System.Math.PI * 2 * 0.9);
Assert.IsTrue(res.max > 0);
Assert.IsTrue(res.max < System.Math.PI * 2*0.1);
Assert.IsTrue(res.max < System.Math.PI * 2 * 0.1);
}
[Test]
@ -115,7 +213,7 @@ namespace KLHZ.Trader.Core.Tests
[Test]
public static void CalcPhaseRangeForMax_CosWithShift2()
{
var res = FFT.CalcPhaseRangeFoxMax(0, 1, - System.Math.PI / 2, 0.001);
var res = FFT.CalcPhaseRangeFoxMax(0, 1, -System.Math.PI / 2, 0.001);
Assert.IsTrue(res.min < System.Math.PI / 2);
Assert.IsTrue(res.min > System.Math.PI / 2 * 0.9);
Assert.IsTrue(res.max > System.Math.PI / 2);

View File

@ -1,10 +1,5 @@
using KLHZ.Trader.Core.Math.Declisions.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace KLHZ.Trader.Core.Tests
{
@ -13,14 +8,14 @@ namespace KLHZ.Trader.Core.Tests
[Test]
public static void Test()
{
var da = new List<double>();
var da = new List<decimal>();
var times = new List<DateTime>();
var startDt = DateTime.UtcNow;
for (int i = 0; i < 100; i++)
{
startDt = startDt.AddSeconds(((double)(RandomNumberGenerator.GetInt32(1, 100)))/100);
startDt = startDt.AddSeconds(((double)(RandomNumberGenerator.GetInt32(1, 100))) / 100);
times.Add(startDt);
da.Add((float)System.Math.Sin(0.01 * i) + (float)System.Math.Cos(0.01 * i));
da.Add((decimal)System.Math.Sin(0.01 * i) + (decimal)System.Math.Cos(0.01 * i));
}
var res = SignalProcessing.InterpolateData(times.ToArray(), da.ToArray(), TimeSpan.FromSeconds(5));

View File

@ -10,6 +10,8 @@
CloseLong = 200,
CloseLongReal = 201,
OpenShort = 300,
OpenShortReal = 301,
CloseShort = 400,
CloseShortReal = 401,
}
}

View File

@ -1,4 +1,5 @@
using KLHZ.Trader.Core.Common;
using Google.Protobuf.WellKnownTypes;
using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
@ -10,6 +11,7 @@ using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using KLHZ.Trader.Core.Exchange.Models.Configs;
using KLHZ.Trader.Core.Exchange.Models.Trading;
using KLHZ.Trader.Core.Exchange.Utils;
using KLHZ.Trader.Core.Math.Declisions.Dtos.FFT.Enums;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
@ -18,8 +20,8 @@ using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Threading.Channels;
using Telegram.Bot.Types;
using Tinkoff.InvestApi;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.AssetType;
namespace KLHZ.Trader.Core.Exchange.Services
@ -29,8 +31,8 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly IDataBus _dataBus;
private readonly TraderDataProvider _tradeDataProvider;
private readonly ILogger<Trader> _logger;
private readonly ConcurrentDictionary<string, DateTime> OpeningStops = new();
private readonly ConcurrentDictionary<string, DateTime> ClosingStops = new();
private readonly ConcurrentDictionary<string, DateTime> LongOpeningStops = new();
private readonly ConcurrentDictionary<string, DateTime> ShortClosingStops = new();
private readonly ConcurrentDictionary<string, InstrumentSettings> Leverages = new();
private readonly decimal _futureComission;
@ -72,14 +74,86 @@ namespace KLHZ.Trader.Core.Exchange.Services
_ = ProcessPrices();
}
public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(INewPrice message)
{
var data2 = await _tradeDataProvider.GetData(message.Figi, TimeSpan.FromHours(1.5));
if (!data2.isFullIntervalExists)
{
data2 = await _tradeDataProvider.GetData(message.Figi, TimeSpan.FromHours(1));
}
if (!data2.isFullIntervalExists)
{
data2 = await _tradeDataProvider.GetData(message.Figi, TimeSpan.FromHours(0.75));
}
return data2;
}
private async ValueTask<ValueAmplitudePosition> CheckPosition((DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists) data, INewPrice message)
{
var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
var position = ValueAmplitudePosition.None;
var fft = await _tradeDataProvider.GetFFtResult(message.Figi);
if (fft.IsEmpty || (currentTime - fft.LastTime).TotalSeconds > 90)
{
if (data.isFullIntervalExists)
{
var interpolatedData = SignalProcessing.InterpolateData(data.timestamps, data.prices, TimeSpan.FromSeconds(5));
fft = FFT.Analyze(interpolatedData.timestamps, interpolatedData.values, message.Figi, TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(40));
await _tradeDataProvider.SetFFtResult(fft);
}
}
else
{
position = FFT.Check(fft, message.Time);
if (position == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.UpperThen20Decil)
{
await LogPrice(message, "upper10percent", message.Value);
await LogPrice(message, "upper30percent", message.Value);
}
if (position == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.LowerThenMediana)
{
await LogPrice(message, "lower30percent", message.Value);
}
}
return position;
}
private async Task ProcessPrices()
{
var pricesCache = new Dictionary<string, List<INewPrice>>();
while (await _pricesChannel.Reader.WaitToReadAsync())
{
var message = await _pricesChannel.Reader.ReadAsync();
if (message.IsHistoricalData)
{
await _tradeDataProvider.AddData(message, TimeSpan.FromHours(6));
if (!pricesCache.TryGetValue(message.Figi, out var list))
{
list = new List<INewPrice>();
pricesCache[message.Figi] = list;
}
list.Add(message);
if ((list.Last().Time - list.First().Time).TotalSeconds < 0.5)
{
list.Add(message);
continue;
}
else
{
message = new PriceChange()
{
Figi = message.Figi,
Ticker = message.Ticker,
Count = message.Count,
Direction = message.Direction,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Value = list.Sum(l => l.Value) / list.Count
};
list.Clear();
}
}
if (_tradingInstrumentsFigis.Contains(message.Figi))
{
@ -90,14 +164,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
if (message.Figi == "FUTIMOEXF000")
{
ProcessStops(message, currentTime);
var windowMaxSize = 1000;
var windowMaxSize = 2000;
await SellAssetsIfNeed(message);
var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize);
var state = ExchangeScheduler.GetCurrentState(message.Time);
await ProcessClearing(data, state, message);
await ProcessNewPriceIMOEXF(data, state, message, windowMaxSize);
await ProcessNewPriceIMOEXF2(data, state, message, windowMaxSize);
}
}
catch (Exception ex)
@ -112,6 +185,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
{
if (!BotModeSwitcher.CanSell())
{
_logger.LogWarning("Сброс активов недоступен, т.к. отключены продажи.");
return;
}
var accounts = _tradeDataProvider.Accounts.Values.ToArray();
@ -124,7 +198,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value,
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
var stoppingKey = message.Figi + asset.AccountId;
if (message.Time - asset.BoughtAt > TimeSpan.FromMinutes(4) && profit < -66m && !ClosingStops.ContainsKey(stoppingKey))
if (message.Time - asset.BoughtAt > TimeSpan.FromMinutes(4) && profit < -66m)
{
var command = new TradeCommand()
{
@ -139,28 +213,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
await _dataBus.Broadcast(command);
_logger.LogWarning("Сброс актива {figi}! id команды {commandId} Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
OpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(10);
ClosingStops[stoppingKey] = DateTime.UtcNow.AddSeconds(30);
await LogDeclision(DeclisionTradeAction.CloseLong, message, profit);
await LogDeclision(DeclisionTradeAction.CloseLongReal, message, profit);
}
if (message.Time - asset.BoughtAt > TimeSpan.FromHours(4) && profit > 100 && !ClosingStops.ContainsKey(stoppingKey))
{
var command = new TradeCommand()
{
AccountId = asset.AccountId,
Figi = message.Figi,
CommandType = asset.Count < 0 ? Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy
: Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell,
Count = (long)asset.Count,
RecomendPrice = null,
EnableMargin = false,
};
await _dataBus.Broadcast(command);
_logger.LogWarning("Сброс актива {figi}! id команды {commandId} Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
ClosingStops[stoppingKey] = DateTime.UtcNow.AddSeconds(30);
await LogDeclision(DeclisionTradeAction.CloseLong, message, profit);
await LogDeclision(DeclisionTradeAction.CloseLongReal, message, profit);
}
@ -168,22 +220,57 @@ namespace KLHZ.Trader.Core.Exchange.Services
}
}
private async Task ProcessNewPriceIMOEXF((DateTime[] timestamps, decimal[] prices) data,
ExchangeState state,
private async Task<TradingEvent> CheckByWindowAverageMean((DateTime[] timestamps, decimal[] prices) data,
INewPrice message, int windowMaxSize)
{
var res = TradingEvent.None;
var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices,
windowMaxSize, 30, 180, TimeSpan.FromSeconds(20), -1m, 2m);
res |= resultMoveAvFull.events;
windowMaxSize, 30, 180, TimeSpan.FromSeconds(20), -1m, 2m);
if (resultMoveAvFull.bigWindowAv != 0)
{
await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv);
await LogPrice(message, Constants.SmallWindowCrossingAverageProcessor, resultMoveAvFull.smallWindowAv);
}
return resultMoveAvFull.events;
}
private async Task<TradingEvent> CheckByWindowAverageMeanForShotrs((DateTime[] timestamps, decimal[] prices) data,
INewPrice message, int windowMaxSize)
{
var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices,
windowMaxSize, 30, 240, TimeSpan.FromSeconds(20), -1m, 1m);
if (resultMoveAvFull.bigWindowAv != 0)
{
//await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv);
//await LogPrice(message, Constants.SmallWindowCrossingAverageProcessor, resultMoveAvFull.smallWindowAv);
}
return resultMoveAvFull.events;
}
private Task<TradingEvent> CheckByLocalTrends((DateTime[] timestamps, decimal[] prices) data,
INewPrice message, int windowMaxSize)
{
var res = TradingEvent.None;
if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(20), 1, out var resLocalTrends))
{
res |= (resLocalTrends & TradingEvent.UptrendStart);
if ((resLocalTrends & TradingEvent.UptrendStart) == TradingEvent.UptrendStart)
{
res |= TradingEvent.DowntrendEnd;
}
}
//if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(30), 2, out var resLocalTrends2))
//{
// res |= (resLocalTrends & TradingEvent.DowntrendEnd);
//}
//if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(20), 2.5, out var resLocalTrends3))
//{
// res |= (resLocalTrends & TradingEvent.DowntrendStart);
//}
return Task.FromResult(res);
}
private async Task<decimal?> GetAreasRelation((DateTime[] timestamps, decimal[] prices) data, INewPrice message)
{
var areasRel = -1m;
if (ShapeAreaCalculator.TryGetAreasRelation(data.timestamps, data.prices, message.Value, Constants.AreasRelationWindow, out var rel))
{
@ -196,59 +283,99 @@ namespace KLHZ.Trader.Core.Exchange.Services
areasRel = (decimal)areas.Sum(a => a.Value) / areas.Length;
await LogPrice(message, Constants.AreasRelationProcessor, areasRel);
return areasRel > 0 ? areasRel : null;
}
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart
&& !OpeningStops.TryGetValue(message.Figi, out _)
return null;
}
private async Task<ValueAmplitudePosition> CheckPosition(INewPrice message)
{
var data2 = await GetData(message);
var position = await CheckPosition(data2, message);
return position;
}
private async Task<decimal?> CalcTrendDiff(INewPrice message)
{
var data = await _tradeDataProvider.GetData(message.Figi, TimeSpan.FromHours(1));
if (data.isFullIntervalExists && LocalTrends.TryCalcTrendDiff(data.timestamps,data.prices, out var res))
{
return res;
}
return null;
}
private async Task ProcessNewPriceIMOEXF2((DateTime[] timestamps, decimal[] prices) data,
ExchangeState state,
INewPrice message, int windowMaxSize)
{
if (data.timestamps.Length <= 4)
{
return;
}
var mavTask = CheckByWindowAverageMean(data, message, windowMaxSize);
var mavTaskShorts = CheckByWindowAverageMeanForShotrs(data, message, windowMaxSize);
var ltTask = CheckByLocalTrends(data, message, windowMaxSize);
var areasTask = GetAreasRelation(data, message);
var positionTask = CheckPosition(message);
var trendTask = CalcTrendDiff(message);
await Task.WhenAll(mavTask, ltTask, areasTask, positionTask, trendTask, mavTaskShorts);
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
var res = mavTask.Result | ltTask.Result;
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart
&& !LongOpeningStops.ContainsKey(message.Figi)
&& trendTask.Result.HasValue
&& System.Math.Abs(trendTask.Result.Value)<6
&& state == ExchangeState.Open
&& data.timestamps.Length > 1
&& (data.timestamps[data.timestamps.Length - 1] - data.timestamps[data.timestamps.Length - 2] < TimeSpan.FromMinutes(1))
&& areasTask.Result.HasValue
&& (areasTask.Result.Value >= 20 && areasTask.Result.Value < 75)
&& (positionTask.Result == ValueAmplitudePosition.LowerThenMediana)
)
{
if (areasRel >= 20 && areasRel < 75)
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
{
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
var accounts = _tradeDataProvider.Accounts
.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
.ToArray();
var loggedDeclisions = 0;
foreach (var acc in accounts)
{
var accounts = _tradeDataProvider.Accounts
.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
.ToArray();
var loggedDeclisions = 0;
foreach (var acc in accounts)
if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart))
{
if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart))
if (RandomNumberGenerator.GetInt32(100) > 50)
{
if (RandomNumberGenerator.GetInt32(100) > 50)
var command = new TradeCommand()
{
var command = new TradeCommand()
{
AccountId = acc.Value.AccountId,
Figi = message.Figi,
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy,
Count = 1,
RecomendPrice = null,
};
await _dataBus.Broadcast(command);
_logger.LogWarning("Покупка актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
if (loggedDeclisions == 0)
{
await LogDeclision(DeclisionTradeAction.OpenLongReal, message);
OpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(1);
loggedDeclisions++;
}
AccountId = acc.Value.AccountId,
Figi = message.Figi,
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy,
Count = 1,
RecomendPrice = null,
};
await _dataBus.Broadcast(command);
_logger.LogWarning("Покупка актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
if (loggedDeclisions == 0)
{
await LogDeclision(DeclisionTradeAction.OpenLongReal, message);
LongOpeningStops[message.Figi] = message.Time.AddMinutes(1);
loggedDeclisions++;
}
}
}
}
await LogDeclision(DeclisionTradeAction.OpenLong, message);
}
}
if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
await LogDeclision(DeclisionTradeAction.OpenLong, message);
}
if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd && positionTask.Result != ValueAmplitudePosition.LowerThenMediana)
{
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
var loggedDeclisions = 0;
if (!message.IsHistoricalData && BotModeSwitcher.CanSell())
{
var loggedDeclisions = 0;
var assetsForClose = _tradeDataProvider.Accounts
.SelectMany(a => a.Value.Assets.Values)
.Where(a => a.Figi == message.Figi && a.Count > 0)
@ -268,9 +395,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
}
var stoppingKey = message.Figi + asset.AccountId;
if (profit > 0 && !ClosingStops.ContainsKey(stoppingKey))
if (profit > 0)
{
ClosingStops[stoppingKey] = DateTime.UtcNow.AddSeconds(30);
//ClosingStops[stoppingKey] = DateTime.UtcNow.AddSeconds(30);
var command = new TradeCommand()
{
AccountId = asset.AccountId,
@ -293,6 +420,98 @@ namespace KLHZ.Trader.Core.Exchange.Services
}
await LogDeclision(DeclisionTradeAction.CloseLong, message);
}
if ((mavTaskShorts.Result & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
{
if (trendTask.Result.HasValue && trendTask.Result.Value < -3)
{
if (!message.IsHistoricalData)
{
var accounts = _tradeDataProvider.Accounts
.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
.ToArray();
var loggedDeclisions = 0;
foreach (var acc in accounts)
{
if (BotModeSwitcher.CanSell())
{
if (RandomNumberGenerator.GetInt32(100) > 50)
{
var command = new TradeCommand()
{
AccountId = acc.Value.AccountId,
Figi = message.Figi,
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell,
Count = 1,
RecomendPrice = null,
EnableMargin = true
};
await _dataBus.Broadcast(command);
_logger.LogWarning("Открытие шорта {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
if (loggedDeclisions == 0)
{
await LogDeclision(DeclisionTradeAction.OpenShortReal, message);
loggedDeclisions++;
}
}
}
}
}
await LogDeclision(DeclisionTradeAction.OpenShort, message);
}
}
if ((res & TradingEvent.DowntrendEnd) == TradingEvent.DowntrendEnd)
{
if (!ShortClosingStops.ContainsKey(message.Figi))
{
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
{
var loggedDeclisions = 0;
var assetsForClose = _tradeDataProvider.Accounts
.SelectMany(a => a.Value.Assets.Values)
.Where(a => a.Figi == message.Figi && a.Count < 0)
.ToArray();
foreach (var asset in assetsForClose)
{
var profit = 0m;
if (assetType == AssetType.Futures)
{
profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value,
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
}
if (profit > 0)
{
var command = new TradeCommand()
{
AccountId = asset.AccountId,
Figi = message.Figi,
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy,
Count = System.Math.Abs((long)asset.Count),
RecomendPrice = null,
EnableMargin = false,
};
await _dataBus.Broadcast(command);
_logger.LogWarning("Продажа актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
if (loggedDeclisions == 0)
{
loggedDeclisions++;
await LogDeclision(DeclisionTradeAction.CloseShortReal, message, profit);
}
}
}
}
if (message.IsHistoricalData)
{
ShortClosingStops[message.Figi] = message.Time.AddSeconds(30);
}
await LogDeclision(DeclisionTradeAction.CloseShort, message);
}
}
}
private async Task ProcessClearing((DateTime[] timestamps, decimal[] prices) data, ExchangeState state, INewPrice message)
@ -308,18 +527,18 @@ namespace KLHZ.Trader.Core.Exchange.Services
private void ProcessStops(INewPrice message, DateTime currentTime)
{
if (OpeningStops.TryGetValue(message.Figi, out var dt))
if (LongOpeningStops.TryGetValue(message.Figi, out var dt))
{
if (dt < currentTime)
{
OpeningStops.TryRemove(message.Figi, out _);
LongOpeningStops.TryRemove(message.Figi, out _);
}
}
if (ClosingStops.TryGetValue(message.Figi, out var dt2))
if (ShortClosingStops.TryGetValue(message.Figi, out var dt2))
{
if (dt2 < currentTime)
{
ClosingStops.TryRemove(message.Figi, out _);
ShortClosingStops.TryRemove(message.Figi, out _);
}
}
}

View File

@ -9,6 +9,7 @@ using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using KLHZ.Trader.Core.Exchange.Models.Configs;
using KLHZ.Trader.Core.Math.Declisions.Dtos.FFT;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.EntityFrameworkCore;
@ -34,6 +35,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly string[] _managedAccountsNamePatterns = [];
private readonly string[] _instrumentsFigis = [];
private readonly ConcurrentDictionary<string, FFTAnalyzeResult> _fftResults = new();
private readonly ConcurrentDictionary<string, InstrumentSettings> _instrumentsSettings = new();
private readonly ConcurrentDictionary<string, string> _tickersCache = new();
private readonly ConcurrentDictionary<string, AssetType> _assetTypesCache = new();
@ -57,6 +59,21 @@ namespace KLHZ.Trader.Core.Exchange.Services
}
}
public ValueTask<FFTAnalyzeResult> GetFFtResult(string figi)
{
if (_fftResults.TryGetValue(figi, out var res))
{
return ValueTask.FromResult(res);
}
return ValueTask.FromResult<FFTAnalyzeResult>(FFTAnalyzeResult.Empty);
}
public ValueTask SetFFtResult(FFTAnalyzeResult result)
{
_fftResults[result.Key] = result;
return ValueTask.CompletedTask;
}
public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(string figi, TimeSpan timeSpan)
{
if (_historyCash.TryGetValue(figi, out var unit))
@ -221,8 +238,10 @@ namespace KLHZ.Trader.Core.Exchange.Services
internal async Task SyncPortfolio(ManagedAccount account)
{
try
{
await _initSemaphore.WaitAsync(TimeSpan.FromSeconds(5));
var portfolio = await _investApiClient.Operations.GetPortfolioAsync(new PortfolioRequest()
{
AccountId = account.AccountId,
@ -252,6 +271,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
else
{
price = position.AveragePositionPrice;
}
#pragma warning disable CS0612 // Тип или член устарел
@ -291,6 +311,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
{
_logger.LogError(ex, "Ошибка при синхранизации портфеля счёта {accountId}", account.AccountId);
}
_initSemaphore.Release();
}
internal async Task UpdateFuturesPrice(INewPrice newPrice, decimal newPriceValue)

View File

@ -3,10 +3,8 @@ using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading.Channels;
using Telegram.Bot.Types;
using Tinkoff.InvestApi;
using Tinkoff.InvestApi.V1;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
namespace KLHZ.Trader.Core.Exchange.Services
{

View File

@ -7,7 +7,6 @@ using KLHZ.Trader.Core.Exchange.Services;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
namespace KLHZ.Trader.Service.Controllers
{
@ -31,12 +30,12 @@ namespace KLHZ.Trader.Service.Controllers
{
try
{
var time1 = DateTime.UtcNow.AddDays(-30);
var time2 = DateTime.UtcNow.AddMinutes(18);
var time1 = DateTime.UtcNow.AddDays(-16.5);
//var time2 = DateTime.UtcNow.AddMinutes(18);
using var context1 = await _dbContextFactory.CreateDbContextAsync();
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var data = await context1.PriceChanges
.Where(c => c.Figi == figi && c.Time >= time1 && c.Time <= time2)
.Where(c => c.Figi == figi && c.Time >= time1)
.OrderBy(c => c.Time)
.Select(c => new NewPriceMessage()
{
@ -220,11 +219,11 @@ namespace KLHZ.Trader.Service.Controllers
.ToArrayAsync();
var buffer = new List<ProcessedPrice>();
var res = SignalProcessing.InterpolateData(data.Select(d => d.Time).ToArray(), data.Select(d => (double)d.Value).ToArray(),
var res = SignalProcessing.InterpolateData(data.Select(d => d.Time).ToArray(), data.Select(d => d.Value).ToArray(),
TimeSpan.FromSeconds(1));
for (int i=0;i< res.Item1.Length; i++)
for (int i = 0; i < res.Item1.Length; i++)
{
buffer.Add(new ProcessedPrice()
{
@ -234,7 +233,7 @@ namespace KLHZ.Trader.Service.Controllers
Count = 1,
Direction = 0,
Time = res.Item1[i],
Value =(decimal) res.Item2[i]
Value = (decimal)res.Item2[i]
});
}
await context1.ProcessedPrices.AddRangeAsync(buffer);
@ -262,12 +261,12 @@ namespace KLHZ.Trader.Service.Controllers
.ToArrayAsync();
var buffer = new List<ProcessedPrice>();
var values = data.Select(d => (double)d.Value).ToArray();
var values = data.Select(d => d.Value).ToArray();
var times = data.Select(d => d.Time).ToArray();
var res = SignalProcessing.InterpolateData(times, values,
TimeSpan.FromSeconds(10));
FFT.GetMainHarmonictPeriod(res.Item2, res.Item1.Last() - res.Item1.First());
//FFT.GetMainHarmonictPeriod(res.Item2, res.Item1.Last() - res.Item1.First());
}
catch (Exception ex)