Compare commits
3 Commits
d0ec2af488
...
57dfa72f36
Author | SHA1 | Date |
---|---|---|
|
57dfa72f36 | |
|
efb6f8b64f | |
|
2b9f7cf332 |
|
@ -4,7 +4,7 @@
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
_1_Minute = 1,
|
_1_Minute = 1,
|
||||||
_2_Minutes = 2,
|
_5_Minutes = 2,
|
||||||
_15_Minutes = 15,
|
_15_Minutes = 15,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
ShortOpen = 16,
|
ShortOpen = 16,
|
||||||
UptrendEnd = 32,
|
UptrendEnd = 32,
|
||||||
UptrendStart = 64,
|
UptrendStart = 64,
|
||||||
HorisontTrend = 128,
|
DowntrendEnd = 128,
|
||||||
|
DowntrendStart = 256,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace KLHZ.Trader.Core.Math.Declisions.Dtos.FFT.Enums
|
||||||
|
{
|
||||||
|
public enum ValueAmplitudePosition
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Middle = 1,
|
||||||
|
UpperThen30Decil = 10,
|
||||||
|
LowerThenMediana = -50,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
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 Upper30Decil { 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" };
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -43,9 +43,9 @@ namespace KLHZ.Trader.Core.Math.Declisions.Dtos
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case TimeWindowCacheType._2_Minutes:
|
case TimeWindowCacheType._5_Minutes:
|
||||||
{
|
{
|
||||||
return TimeSpan.FromMinutes(2);
|
return TimeSpan.FromMinutes(5);
|
||||||
}
|
}
|
||||||
case TimeWindowCacheType._15_Minutes:
|
case TimeWindowCacheType._15_Minutes:
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,8 +9,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
|
||||||
{
|
{
|
||||||
public class PriceHistoryCacheUnit2 : IPriceHistoryCacheUnit
|
public class PriceHistoryCacheUnit2 : IPriceHistoryCacheUnit
|
||||||
{
|
{
|
||||||
public const int CacheMaxLength = 1500;
|
public const int CacheMaxLength = 30000;
|
||||||
private const int _arrayMaxLength = 5000;
|
private const int _arrayMaxLength = 60000;
|
||||||
|
|
||||||
public string Figi { get; init; }
|
public string Figi { get; init; }
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
|
||||||
private readonly decimal[] Prices = new decimal[_arrayMaxLength];
|
private readonly decimal[] Prices = new decimal[_arrayMaxLength];
|
||||||
private readonly DateTime[] Timestamps = new DateTime[_arrayMaxLength];
|
private readonly DateTime[] Timestamps = new DateTime[_arrayMaxLength];
|
||||||
private readonly ConcurrentDictionary<string, TimeWindowCacheItem> _1_minTimeWindows = new();
|
private readonly ConcurrentDictionary<string, TimeWindowCacheItem> _1_minTimeWindows = new();
|
||||||
private readonly ConcurrentDictionary<string, TimeWindowCacheItem> _2_minTimeWindows = new();
|
private readonly ConcurrentDictionary<string, TimeWindowCacheItem> _5_minTimeWindows = new();
|
||||||
private readonly ConcurrentDictionary<string, TimeWindowCacheItem> _15_minTimeWindows = new();
|
private readonly ConcurrentDictionary<string, TimeWindowCacheItem> _15_minTimeWindows = new();
|
||||||
|
|
||||||
private int _length = 0;
|
private int _length = 0;
|
||||||
|
@ -85,9 +85,9 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
|
||||||
{
|
{
|
||||||
switch (timeWindowCacheType)
|
switch (timeWindowCacheType)
|
||||||
{
|
{
|
||||||
case TimeWindowCacheType._2_Minutes:
|
case TimeWindowCacheType._5_Minutes:
|
||||||
{
|
{
|
||||||
return _2_minTimeWindows;
|
return _5_minTimeWindows;
|
||||||
}
|
}
|
||||||
case TimeWindowCacheType._15_Minutes:
|
case TimeWindowCacheType._15_Minutes:
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
using MathNet.Numerics.IntegralTransforms;
|
using MathNet.Numerics.IntegralTransforms;
|
||||||
|
|
||||||
|
@ -6,28 +7,102 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
|
||||||
{
|
{
|
||||||
public static class FFT
|
public static class FFT
|
||||||
{
|
{
|
||||||
public static void Test()
|
public static ValueAmplitudePosition Check(FFTAnalyzeResult fftData, DateTime timestamp)
|
||||||
{
|
{
|
||||||
var da = new List<float>();
|
var value = (decimal)CalcAmplitude(fftData.Harmonics, fftData.StartTime, timestamp);
|
||||||
for (int i = 0; i < 1000; i++)
|
var value2 = (decimal)CalcExtremum(fftData.Harmonics, fftData.StartTime, timestamp);
|
||||||
|
if (value > fftData.Upper30Decil)
|
||||||
{
|
{
|
||||||
da.Add((float)System.Math.Sin(0.01 * i) + (float)System.Math.Cos(0.01 * i));
|
return ValueAmplitudePosition.UpperThen30Decil;
|
||||||
|
}
|
||||||
|
else if (value < fftData.Mediana)
|
||||||
|
{
|
||||||
|
return ValueAmplitudePosition.LowerThenMediana;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ValueAmplitudePosition.Middle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var start = da.ToArray();
|
public static FFTAnalyzeResult Analyze(DateTime[] timestamps, decimal[] values, string key, TimeSpan minPeriod, TimeSpan maxPeriod)
|
||||||
var arrv = da.Select(d => new Complex32(d, 0)).ToArray();
|
{
|
||||||
|
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],
|
||||||
|
Upper30Decil = newValues[(int)(newValues.Length * 0.7)],
|
||||||
|
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);
|
Fourier.Forward(arrv);
|
||||||
|
var res = new List<Harmonic>();
|
||||||
Fourier.Inverse(arrv);
|
for (int i = 1; i < arrv.Length; i++)
|
||||||
var res = arrv.Select(a => a.Real).ToArray();
|
|
||||||
|
|
||||||
for (int i = 0; i < 1000; i++)
|
|
||||||
{
|
{
|
||||||
var d = res[i] - start[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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TimeSpan GetMainHarmonictPeriod(double[] data, TimeSpan period)
|
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();
|
var arrv = data.Select(d => new Complex32((float)d, 0)).ToArray();
|
||||||
Fourier.Forward(arrv);
|
Fourier.Forward(arrv);
|
||||||
|
@ -70,59 +145,25 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
|
||||||
return CaclHarmonycPeriod(period, data.Length, kmax);
|
return CaclHarmonycPeriod(period, data.Length, kmax);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Harmonic[] GetHarmonics(double[] data, TimeSpan period, int count)
|
internal static TimeSpan CaclHarmonycPeriod(TimeSpan signalLength, int signalLengthItems, int harmonyNumber)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var fdiscretisation = signalLengthItems / signalLength.TotalSeconds;
|
var fdiscretisation = signalLengthItems / signalLength.TotalSeconds;
|
||||||
var fharm = harmonyNumber * fdiscretisation / signalLengthItems;
|
var fharm = harmonyNumber * fdiscretisation / signalLengthItems;
|
||||||
return TimeSpan.FromSeconds(1 / fharm);
|
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];
|
var item = spectr[harmonyNumber];
|
||||||
return System.Math.Atan(item.Imaginary / item.Real);
|
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);
|
return CalcPhaseRange(aSin, aCos, initPhase, level, CheckMinValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class LocalTrends
|
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)
|
double meanfullDiff, out TradingEvent res)
|
||||||
{
|
{
|
||||||
res = TradingEvent.None;
|
res = TradingEvent.None;
|
||||||
|
@ -51,6 +51,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (x1.Count > 1 && x2.Count > 1)
|
||||||
|
{
|
||||||
var line1 = Fit.Line(x1.ToArray(), y1.ToArray());
|
var line1 = Fit.Line(x1.ToArray(), y1.ToArray());
|
||||||
var line2 = Fit.Line(x2.ToArray(), y2.ToArray());
|
var line2 = Fit.Line(x2.ToArray(), y2.ToArray());
|
||||||
foreach (var x in x1)
|
foreach (var x in x1)
|
||||||
|
@ -71,12 +73,22 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
|
||||||
{
|
{
|
||||||
res |= TradingEvent.UptrendEnd;
|
res |= TradingEvent.UptrendEnd;
|
||||||
}
|
}
|
||||||
|
else if (diff1 <= -meanfullDiff && diff2 >= 0)
|
||||||
|
{
|
||||||
|
res |= TradingEvent.DowntrendEnd;
|
||||||
|
}
|
||||||
|
else if (diff1 >= 0 && diff2 <= -meanfullDiff)
|
||||||
|
{
|
||||||
|
res |= TradingEvent.DowntrendStart;
|
||||||
|
}
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return success;
|
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;
|
diff = 0;
|
||||||
if (times.Length <= 1 || times.Length != prices.Length)
|
if (times.Length <= 1 || times.Length != prices.Length)
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
{
|
{
|
||||||
public static class SignalProcessing
|
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 res2 = new List<DateTime>();
|
||||||
|
|
||||||
var startTime = new DateTime(timestamps[0].Year, timestamps[0].Month, timestamps[0].Day, 0, 0, 0, DateTimeKind.Utc);
|
var startTime = new DateTime(timestamps[0].Year, timestamps[0].Month, timestamps[0].Day, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
for (int i = 0; i < totalSteps; i++)
|
for (int i = 0; i < totalSteps; i++)
|
||||||
{
|
{
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var sum = 0d;
|
var sum = 0m;
|
||||||
|
|
||||||
for (int i1 = bound; i1 < timestamps.Length; i1++)
|
for (int i1 = bound; i1 < timestamps.Length; i1++)
|
||||||
{
|
{
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
}
|
}
|
||||||
else if (bound < timestamps.Length - 2)
|
else if (bound < timestamps.Length - 2)
|
||||||
{
|
{
|
||||||
res.Add(res.Last());
|
res.Add(values[bound]);
|
||||||
res2.Add(secondBound);
|
res2.Add(secondBound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,82 @@
|
||||||
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
||||||
using MathNet.Numerics;
|
using MathNet.Numerics;
|
||||||
using MathNet.Numerics.IntegralTransforms;
|
using MathNet.Numerics.IntegralTransforms;
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace KLHZ.Trader.Core.Tests
|
namespace KLHZ.Trader.Core.Tests
|
||||||
{
|
{
|
||||||
public class FFTTests
|
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]
|
[Test]
|
||||||
public static void Test()
|
public static void Test()
|
||||||
{
|
{
|
||||||
|
@ -14,32 +84,60 @@ namespace KLHZ.Trader.Core.Tests
|
||||||
var da2 = new List<float>();
|
var da2 = new List<float>();
|
||||||
var dates = new List<DateTime>();
|
var dates = new List<DateTime>();
|
||||||
var dt = DateTime.UtcNow;
|
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);
|
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);
|
dates.Add(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
var start = da.ToArray();
|
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));
|
||||||
|
|
||||||
|
var damax = da.Max();
|
||||||
|
var damin = da.Min();
|
||||||
|
|
||||||
|
for (int i = 0; i < da.Count; i++)
|
||||||
|
{
|
||||||
|
da[i] = da[i] / (damax - damin);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var d in dates)
|
foreach (var d in dates)
|
||||||
{
|
{
|
||||||
da2.Add((float)FFT.Calc(harms, dates.First(), dates.Last(), d));
|
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 tem = arrv.Select(a => Complex32.Abs(a)).ToArray();
|
||||||
//var s = tem.Sum();
|
//var s = tem.Sum();
|
||||||
//Fourier.Inverse(arrv);
|
//Fourier.Inverse(arrv);
|
||||||
//var res = arrv.Select(a => a.Real).ToArray();
|
//var res = arrv.Select(a => a.Real).ToArray();
|
||||||
|
var diffs = new List<float>();
|
||||||
//for (int i = 0; i < 1000; i++)
|
for (int i = 0; i < da2.Count; i++)
|
||||||
//{
|
{
|
||||||
// var d = res[i] - start[i];
|
var diff = (da2[i] - da[i]) / (damax - damin);
|
||||||
//}
|
diffs.Add(diff);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace KLHZ.Trader.Core.Tests
|
namespace KLHZ.Trader.Core.Tests
|
||||||
{
|
{
|
||||||
|
@ -13,14 +8,14 @@ namespace KLHZ.Trader.Core.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public static void Test()
|
public static void Test()
|
||||||
{
|
{
|
||||||
var da = new List<double>();
|
var da = new List<decimal>();
|
||||||
var times = new List<DateTime>();
|
var times = new List<DateTime>();
|
||||||
var startDt = DateTime.UtcNow;
|
var startDt = DateTime.UtcNow;
|
||||||
for (int i = 0; i < 100; i++)
|
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);
|
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));
|
var res = SignalProcessing.InterpolateData(times.ToArray(), da.ToArray(), TimeSpan.FromSeconds(5));
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
CloseLong = 200,
|
CloseLong = 200,
|
||||||
CloseLongReal = 201,
|
CloseLongReal = 201,
|
||||||
OpenShort = 300,
|
OpenShort = 300,
|
||||||
|
OpenShortReal = 301,
|
||||||
CloseShort = 400,
|
CloseShort = 400,
|
||||||
|
CloseShortReal = 401,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
internal static class Constants
|
internal static class Constants
|
||||||
{
|
{
|
||||||
internal const string _1minCacheKey = "1min";
|
internal const string _1minCacheKey = "1min";
|
||||||
|
internal const string _1minSellCacheKey = "1min_sell";
|
||||||
|
internal const string _1minBuyCacheKey = "1min_buy";
|
||||||
internal const string BigWindowCrossingAverageProcessor = "Trader_big";
|
internal const string BigWindowCrossingAverageProcessor = "Trader_big";
|
||||||
internal const string SmallWindowCrossingAverageProcessor = "Trader_small";
|
internal const string SmallWindowCrossingAverageProcessor = "Trader_small";
|
||||||
internal const string AreasRelationProcessor = "balancescalc30min";
|
internal const string AreasRelationProcessor = "balancescalc30min";
|
||||||
|
|
|
@ -10,6 +10,7 @@ using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
|
||||||
using KLHZ.Trader.Core.Exchange.Models.Configs;
|
using KLHZ.Trader.Core.Exchange.Models.Configs;
|
||||||
using KLHZ.Trader.Core.Exchange.Models.Trading;
|
using KLHZ.Trader.Core.Exchange.Models.Trading;
|
||||||
using KLHZ.Trader.Core.Exchange.Utils;
|
using KLHZ.Trader.Core.Exchange.Utils;
|
||||||
|
using KLHZ.Trader.Core.Math.Declisions.Dtos.FFT.Enums;
|
||||||
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
@ -19,7 +20,6 @@ using System.Collections.Concurrent;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using Tinkoff.InvestApi;
|
using Tinkoff.InvestApi;
|
||||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
|
|
||||||
using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.AssetType;
|
using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.AssetType;
|
||||||
|
|
||||||
namespace KLHZ.Trader.Core.Exchange.Services
|
namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
|
@ -29,8 +29,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
private readonly IDataBus _dataBus;
|
private readonly IDataBus _dataBus;
|
||||||
private readonly TraderDataProvider _tradeDataProvider;
|
private readonly TraderDataProvider _tradeDataProvider;
|
||||||
private readonly ILogger<Trader> _logger;
|
private readonly ILogger<Trader> _logger;
|
||||||
private readonly ConcurrentDictionary<string, DateTime> OpeningStops = new();
|
private readonly ConcurrentDictionary<string, DateTime> LongOpeningStops = new();
|
||||||
private readonly ConcurrentDictionary<string, DateTime> ClosingStops = new();
|
private readonly ConcurrentDictionary<string, DateTime> LongClosingStops = new();
|
||||||
|
private readonly ConcurrentDictionary<string, DateTime> ShortClosingStops = new();
|
||||||
private readonly ConcurrentDictionary<string, InstrumentSettings> Leverages = new();
|
private readonly ConcurrentDictionary<string, InstrumentSettings> Leverages = new();
|
||||||
|
|
||||||
private readonly decimal _futureComission;
|
private readonly decimal _futureComission;
|
||||||
|
@ -72,15 +73,116 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
_ = ProcessPrices();
|
_ = 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.UpperThen30Decil)
|
||||||
|
{
|
||||||
|
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()
|
private async Task ProcessPrices()
|
||||||
{
|
{
|
||||||
|
var pricesCache = new Dictionary<string, List<INewPrice>>();
|
||||||
while (await _pricesChannel.Reader.WaitToReadAsync())
|
while (await _pricesChannel.Reader.WaitToReadAsync())
|
||||||
{
|
{
|
||||||
var message = await _pricesChannel.Reader.ReadAsync();
|
var message = await _pricesChannel.Reader.ReadAsync();
|
||||||
if (message.IsHistoricalData)
|
if (message.IsHistoricalData)
|
||||||
{
|
{
|
||||||
await _tradeDataProvider.AddData(message, TimeSpan.FromHours(6));
|
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 (message.Figi == "BBG004730N88")
|
||||||
|
{
|
||||||
|
if (message.Direction == 1)
|
||||||
|
{
|
||||||
|
await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._1minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue()
|
||||||
|
{
|
||||||
|
Time = message.Time,
|
||||||
|
Value = (decimal)message.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (message.Direction == 2)
|
||||||
|
{
|
||||||
|
await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._1minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue()
|
||||||
|
{
|
||||||
|
Time = message.Time,
|
||||||
|
Value = (decimal)message.Count
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
var sberSells = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._1minSellCacheKey);
|
||||||
|
var sberBuys = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._1minBuyCacheKey);
|
||||||
|
var sells = sberSells.Sum(s => s.Value);
|
||||||
|
var buys = sberBuys.Sum(s => s.Value);
|
||||||
|
var su = sells + buys;
|
||||||
|
if (su != 0)
|
||||||
|
{
|
||||||
|
await LogPrice(message, "sellsbuysbalance", (sells / su - 0.5m) * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_tradingInstrumentsFigis.Contains(message.Figi))
|
if (_tradingInstrumentsFigis.Contains(message.Figi))
|
||||||
{
|
{
|
||||||
var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
|
var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
|
||||||
|
@ -90,14 +192,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
if (message.Figi == "FUTIMOEXF000")
|
if (message.Figi == "FUTIMOEXF000")
|
||||||
{
|
{
|
||||||
ProcessStops(message, currentTime);
|
ProcessStops(message, currentTime);
|
||||||
var windowMaxSize = 1000;
|
var windowMaxSize = 2000;
|
||||||
await SellAssetsIfNeed(message);
|
await SellAssetsIfNeed(message);
|
||||||
var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize);
|
var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize);
|
||||||
var state = ExchangeScheduler.GetCurrentState(message.Time);
|
var state = ExchangeScheduler.GetCurrentState(message.Time);
|
||||||
await ProcessClearing(data, state, message);
|
await ProcessClearing(data, state, message);
|
||||||
|
|
||||||
|
await ProcessNewPriceIMOEXF2(data, state, message, windowMaxSize);
|
||||||
await ProcessNewPriceIMOEXF(data, state, message, windowMaxSize);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -112,6 +213,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
{
|
{
|
||||||
if (!BotModeSwitcher.CanSell())
|
if (!BotModeSwitcher.CanSell())
|
||||||
{
|
{
|
||||||
|
_logger.LogWarning("Сброс активов недоступен, т.к. отключены продажи.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var accounts = _tradeDataProvider.Accounts.Values.ToArray();
|
var accounts = _tradeDataProvider.Accounts.Values.ToArray();
|
||||||
|
@ -124,7 +226,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value,
|
var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value,
|
||||||
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
|
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
|
||||||
var stoppingKey = message.Figi + asset.AccountId;
|
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()
|
var command = new TradeCommand()
|
||||||
{
|
{
|
||||||
|
@ -139,28 +241,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
await _dataBus.Broadcast(command);
|
await _dataBus.Broadcast(command);
|
||||||
_logger.LogWarning("Сброс актива {figi}! id команды {commandId} Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
|
_logger.LogWarning("Сброс актива {figi}! id команды {commandId} Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
|
||||||
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
|
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.CloseLong, message, profit);
|
||||||
await LogDeclision(DeclisionTradeAction.CloseLongReal, message, profit);
|
await LogDeclision(DeclisionTradeAction.CloseLongReal, message, profit);
|
||||||
}
|
}
|
||||||
|
@ -168,22 +248,57 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessNewPriceIMOEXF((DateTime[] timestamps, decimal[] prices) data,
|
private async Task<TradingEvent> CheckByWindowAverageMean((DateTime[] timestamps, decimal[] prices) data,
|
||||||
ExchangeState state,
|
|
||||||
INewPrice message, int windowMaxSize)
|
INewPrice message, int windowMaxSize)
|
||||||
{
|
{
|
||||||
var res = TradingEvent.None;
|
|
||||||
var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices,
|
var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices,
|
||||||
windowMaxSize, 30, 180, TimeSpan.FromSeconds(20), -1m, 2m);
|
windowMaxSize, 30, 180, TimeSpan.FromSeconds(20), -1m, 2m);
|
||||||
|
|
||||||
res |= resultMoveAvFull.events;
|
|
||||||
|
|
||||||
if (resultMoveAvFull.bigWindowAv != 0)
|
if (resultMoveAvFull.bigWindowAv != 0)
|
||||||
{
|
{
|
||||||
await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv);
|
await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv);
|
||||||
await LogPrice(message, Constants.SmallWindowCrossingAverageProcessor, resultMoveAvFull.smallWindowAv);
|
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;
|
var areasRel = -1m;
|
||||||
if (ShapeAreaCalculator.TryGetAreasRelation(data.timestamps, data.prices, message.Value, Constants.AreasRelationWindow, out var rel))
|
if (ShapeAreaCalculator.TryGetAreasRelation(data.timestamps, data.prices, message.Value, Constants.AreasRelationWindow, out var rel))
|
||||||
{
|
{
|
||||||
|
@ -196,15 +311,65 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
|
|
||||||
areasRel = (decimal)areas.Sum(a => a.Value) / areas.Length;
|
areasRel = (decimal)areas.Sum(a => a.Value) / areas.Length;
|
||||||
await LogPrice(message, Constants.AreasRelationProcessor, areasRel);
|
await LogPrice(message, Constants.AreasRelationProcessor, areasRel);
|
||||||
|
return areasRel > 0 ? areasRel : null;
|
||||||
}
|
}
|
||||||
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart
|
return null;
|
||||||
&& !OpeningStops.TryGetValue(message.Figi, out _)
|
}
|
||||||
&& state == ExchangeState.Open
|
|
||||||
&& data.timestamps.Length > 1
|
private async Task<ValueAmplitudePosition> CheckPosition(INewPrice message)
|
||||||
&& (data.timestamps[data.timestamps.Length - 1] - data.timestamps[data.timestamps.Length - 2] < TimeSpan.FromMinutes(1))
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
if (areasRel >= 20 && areasRel < 75)
|
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 sberSells = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._1minSellCacheKey);
|
||||||
|
var sberBuys = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._1minBuyCacheKey);
|
||||||
|
var sells = sberSells.Sum(s => s.Value);
|
||||||
|
var buys = sberBuys.Sum(s => s.Value);
|
||||||
|
var su = sells + buys;
|
||||||
|
var dsell = (sells / su - 0.5m) * 2;
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
&& trendTask.Result.Value > -5
|
||||||
|
&& state == ExchangeState.Open
|
||||||
|
&& areasTask.Result.HasValue
|
||||||
|
&& (areasTask.Result.Value >= 20 && areasTask.Result.Value < 75)
|
||||||
|
&& (positionTask.Result == ValueAmplitudePosition.LowerThenMediana)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
|
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
|
||||||
{
|
{
|
||||||
|
@ -232,23 +397,23 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
if (loggedDeclisions == 0)
|
if (loggedDeclisions == 0)
|
||||||
{
|
{
|
||||||
await LogDeclision(DeclisionTradeAction.OpenLongReal, message);
|
await LogDeclision(DeclisionTradeAction.OpenLongReal, message);
|
||||||
OpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(1);
|
LongOpeningStops[message.Figi] = message.Time.AddMinutes(1);
|
||||||
loggedDeclisions++;
|
loggedDeclisions++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await LogDeclision(DeclisionTradeAction.OpenLong, message);
|
await LogDeclision(DeclisionTradeAction.OpenLong, message);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
|
if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
|
||||||
{
|
{
|
||||||
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
|
if (dsell < 0.1m)
|
||||||
var loggedDeclisions = 0;
|
{
|
||||||
if (!message.IsHistoricalData && BotModeSwitcher.CanSell())
|
if (!message.IsHistoricalData && BotModeSwitcher.CanSell())
|
||||||
{
|
{
|
||||||
|
var loggedDeclisions = 0;
|
||||||
var assetsForClose = _tradeDataProvider.Accounts
|
var assetsForClose = _tradeDataProvider.Accounts
|
||||||
.SelectMany(a => a.Value.Assets.Values)
|
.SelectMany(a => a.Value.Assets.Values)
|
||||||
.Where(a => a.Figi == message.Figi && a.Count > 0)
|
.Where(a => a.Figi == message.Figi && a.Count > 0)
|
||||||
|
@ -267,10 +432,10 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value,
|
profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value,
|
||||||
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
|
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);
|
LongClosingStops[message.Figi] = message.Time.AddSeconds(30);
|
||||||
var command = new TradeCommand()
|
var command = new TradeCommand()
|
||||||
{
|
{
|
||||||
AccountId = asset.AccountId,
|
AccountId = asset.AccountId,
|
||||||
|
@ -293,6 +458,100 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
}
|
}
|
||||||
await LogDeclision(DeclisionTradeAction.CloseLong, message);
|
await LogDeclision(DeclisionTradeAction.CloseLong, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mavTaskShorts.Result & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
|
||||||
|
{
|
||||||
|
if (trendTask.Result.HasValue && trendTask.Result.Value < -4)
|
||||||
|
{
|
||||||
|
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)
|
private async Task ProcessClearing((DateTime[] timestamps, decimal[] prices) data, ExchangeState state, INewPrice message)
|
||||||
|
@ -308,18 +567,25 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
|
|
||||||
private void ProcessStops(INewPrice message, DateTime currentTime)
|
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)
|
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)
|
if (dt2 < currentTime)
|
||||||
{
|
{
|
||||||
ClosingStops.TryRemove(message.Figi, out _);
|
ShortClosingStops.TryRemove(message.Figi, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (LongClosingStops.TryGetValue(message.Figi, out var dt3))
|
||||||
|
{
|
||||||
|
if (dt3 < currentTime)
|
||||||
|
{
|
||||||
|
LongClosingStops.TryRemove(message.Figi, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ using KLHZ.Trader.Core.DataLayer.Entities.Prices;
|
||||||
using KLHZ.Trader.Core.Exchange.Extentions;
|
using KLHZ.Trader.Core.Exchange.Extentions;
|
||||||
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
|
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
|
||||||
using KLHZ.Trader.Core.Exchange.Models.Configs;
|
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.Services.Cache;
|
||||||
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
@ -34,6 +35,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
private readonly string[] _managedAccountsNamePatterns = [];
|
private readonly string[] _managedAccountsNamePatterns = [];
|
||||||
private readonly string[] _instrumentsFigis = [];
|
private readonly string[] _instrumentsFigis = [];
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, FFTAnalyzeResult> _fftResults = new();
|
||||||
private readonly ConcurrentDictionary<string, InstrumentSettings> _instrumentsSettings = new();
|
private readonly ConcurrentDictionary<string, InstrumentSettings> _instrumentsSettings = new();
|
||||||
private readonly ConcurrentDictionary<string, string> _tickersCache = new();
|
private readonly ConcurrentDictionary<string, string> _tickersCache = new();
|
||||||
private readonly ConcurrentDictionary<string, AssetType> _assetTypesCache = 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)
|
public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(string figi, TimeSpan timeSpan)
|
||||||
{
|
{
|
||||||
if (_historyCash.TryGetValue(figi, out var unit))
|
if (_historyCash.TryGetValue(figi, out var unit))
|
||||||
|
@ -109,6 +126,16 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._1_Minute);
|
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._1_Minute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask AddDataTo5MinuteWindowCache(string figi, string key, CachedValue data)
|
||||||
|
{
|
||||||
|
if (!_historyCash.TryGetValue(figi, out var unit))
|
||||||
|
{
|
||||||
|
unit = new PriceHistoryCacheUnit2(figi);
|
||||||
|
_historyCash.TryAdd(figi, unit);
|
||||||
|
}
|
||||||
|
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._5_Minutes);
|
||||||
|
}
|
||||||
|
|
||||||
public ValueTask<CachedValue[]> GetDataFrom1MinuteWindowCache(string figi, string key)
|
public ValueTask<CachedValue[]> GetDataFrom1MinuteWindowCache(string figi, string key)
|
||||||
{
|
{
|
||||||
if (_historyCash.TryGetValue(figi, out var cahcheItem))
|
if (_historyCash.TryGetValue(figi, out var cahcheItem))
|
||||||
|
@ -118,6 +145,15 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
return ValueTask.FromResult(Array.Empty<CachedValue>());
|
return ValueTask.FromResult(Array.Empty<CachedValue>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask<CachedValue[]> GetDataFrom5MinuteWindowCache(string figi, string key)
|
||||||
|
{
|
||||||
|
if (_historyCash.TryGetValue(figi, out var cahcheItem))
|
||||||
|
{
|
||||||
|
return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._5_Minutes);
|
||||||
|
}
|
||||||
|
return ValueTask.FromResult(Array.Empty<CachedValue>());
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask AddOrderbook(IOrderbook orderbook)
|
public async ValueTask AddOrderbook(IOrderbook orderbook)
|
||||||
{
|
{
|
||||||
if (!_historyCash.TryGetValue(orderbook.Figi, out var unit))
|
if (!_historyCash.TryGetValue(orderbook.Figi, out var unit))
|
||||||
|
@ -221,8 +257,10 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
|
|
||||||
internal async Task SyncPortfolio(ManagedAccount account)
|
internal async Task SyncPortfolio(ManagedAccount account)
|
||||||
{
|
{
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await _initSemaphore.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
var portfolio = await _investApiClient.Operations.GetPortfolioAsync(new PortfolioRequest()
|
var portfolio = await _investApiClient.Operations.GetPortfolioAsync(new PortfolioRequest()
|
||||||
{
|
{
|
||||||
AccountId = account.AccountId,
|
AccountId = account.AccountId,
|
||||||
|
@ -252,6 +290,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
price = position.AveragePositionPrice;
|
price = position.AveragePositionPrice;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS0612 // Тип или член устарел
|
#pragma warning disable CS0612 // Тип или член устарел
|
||||||
|
@ -291,6 +330,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Ошибка при синхранизации портфеля счёта {accountId}", account.AccountId);
|
_logger.LogError(ex, "Ошибка при синхранизации портфеля счёта {accountId}", account.AccountId);
|
||||||
}
|
}
|
||||||
|
_initSemaphore.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task UpdateFuturesPrice(INewPrice newPrice, decimal newPriceValue)
|
internal async Task UpdateFuturesPrice(INewPrice newPrice, decimal newPriceValue)
|
||||||
|
|
|
@ -3,10 +3,8 @@ using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using Telegram.Bot.Types;
|
|
||||||
using Tinkoff.InvestApi;
|
using Tinkoff.InvestApi;
|
||||||
using Tinkoff.InvestApi.V1;
|
using Tinkoff.InvestApi.V1;
|
||||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
|
|
||||||
|
|
||||||
namespace KLHZ.Trader.Core.Exchange.Services
|
namespace KLHZ.Trader.Core.Exchange.Services
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,7 +7,6 @@ using KLHZ.Trader.Core.Exchange.Services;
|
||||||
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace KLHZ.Trader.Service.Controllers
|
namespace KLHZ.Trader.Service.Controllers
|
||||||
{
|
{
|
||||||
|
@ -27,20 +26,24 @@ namespace KLHZ.Trader.Service.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task Run(string figi)
|
public async Task Run()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var time1 = DateTime.UtcNow.AddDays(-30);
|
var figi1 = "FUTIMOEXF000";
|
||||||
var time2 = DateTime.UtcNow.AddMinutes(18);
|
//var figi1 = "BBG004730N88";
|
||||||
|
var figi2 = "BBG004730N88";
|
||||||
|
//var figi2 = "FUTIMOEXF000";
|
||||||
|
var time1 = DateTime.UtcNow.AddDays(-16.5);
|
||||||
|
//var time2 = DateTime.UtcNow.AddMinutes(18);
|
||||||
using var context1 = await _dbContextFactory.CreateDbContextAsync();
|
using var context1 = await _dbContextFactory.CreateDbContextAsync();
|
||||||
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||||
var data = await context1.PriceChanges
|
var data = await context1.PriceChanges
|
||||||
.Where(c => c.Figi == figi && c.Time >= time1 && c.Time <= time2)
|
.Where(c => (c.Figi == figi1 || c.Figi == figi2) && c.Time >= time1)
|
||||||
.OrderBy(c => c.Time)
|
.OrderBy(c => c.Time)
|
||||||
.Select(c => new NewPriceMessage()
|
.Select(c => new NewPriceMessage()
|
||||||
{
|
{
|
||||||
Figi = figi,
|
Figi = c.Figi,
|
||||||
Ticker = c.Ticker,
|
Ticker = c.Ticker,
|
||||||
Time = c.Time,
|
Time = c.Time,
|
||||||
Value = c.Value,
|
Value = c.Value,
|
||||||
|
@ -220,7 +223,7 @@ namespace KLHZ.Trader.Service.Controllers
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
var buffer = new List<ProcessedPrice>();
|
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));
|
TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
|
|
||||||
|
@ -262,12 +265,12 @@ namespace KLHZ.Trader.Service.Controllers
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
var buffer = new List<ProcessedPrice>();
|
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 times = data.Select(d => d.Time).ToArray();
|
||||||
var res = SignalProcessing.InterpolateData(times, values,
|
var res = SignalProcessing.InterpolateData(times, values,
|
||||||
TimeSpan.FromSeconds(10));
|
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)
|
catch (Exception ex)
|
||||||
|
|
Loading…
Reference in New Issue