фиксация
test / deploy_trader_prod (push) Successful in 3m3s Details

dev
vlad zverzhkhovskiy 2025-09-13 10:52:45 +03:00
parent 9bfff7e4d6
commit 067d7d138d
7 changed files with 284 additions and 90 deletions

View File

@ -0,0 +1,29 @@
using MathNet.Numerics;
using MathNet.Numerics.IntegralTransforms;
namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
public static class FFT
{
public static void Test()
{
var da = new List<float>();
for (int i = 0; i < 1000; i++)
{
da.Add((float)System.Math.Sin(0.01 * i));
}
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++)
{
var d = res[i] - start[i];
}
}
}
}

View File

@ -1,6 +1,5 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Math.Declisions.Dtos;
using MathNet.Numerics;
namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
@ -9,108 +8,97 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
/// </summary>
public static class LocalTrends
{
public static TradingEvent CheckByLocalTrends(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
internal static bool TryGetLocalTrends(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan lastPeriod,
double meanfullDiff, out TradingEvent res)
{
var res = TradingEvent.None;
res |= CheckUptrendStart(times, prices, firstPeriod, secondPeriod, meanfullDiff, boundIndex);
res |= CheckUptrendEnd(times, prices, firstPeriod, secondPeriod, meanfullDiff, boundIndex);
res = TradingEvent.None;
var success = false;
return res;
}
if (times.Length == 0)
{
return success;
}
var x1 = new List<double>();
var y1 = new List<double>();
var x2 = new List<double>();
var y2 = new List<double>();
var y1_approximated = new List<double>();
var y2_approximated = new List<double>();
internal static TradingEvent CheckUptrendStart(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{
var periodStat = GetTwoPeriodsProcessingData(times, prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart < 0.5m * meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff;
return isStartOk && isEndOk && prices[periodStat.Start] - prices[periodStat.End] >= meanfullDiff ? TradingEvent.UptrendStart : TradingEvent.None;
}
internal static TradingEvent CheckUptrendEnd(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{
var periodStat = GetTwoPeriodsProcessingData(times, prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart > 0 && periodStat.DiffStart > 1.5m * meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd < meanfullDiff;
return isStartOk && isEndOk && prices[periodStat.End] - prices[periodStat.Start] >= meanfullDiff ? TradingEvent.UptrendEnd : TradingEvent.None; ;
}
internal static TwoLocalTrendsResultDto GetTwoPeriodsProcessingData(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan lastPeriod, int boundIndex, decimal meanfullDiff)
{
var res = new TwoLocalTrendsResultDto(success: false, 0, 0, 0, 0, 0, TimeSpan.Zero, TimeSpan.Zero);
int count = -1;
var lastTime = times[times.Length - 1];
var bound = -1;
var start = -1;
var end = times.Length - 1;
var firstTime = times[0];
var fullPeriod = firstPeriod + lastPeriod;
for (int i = times.Length - 1; i > -1; i--)
if (lastTime - firstTime > fullPeriod)
{
if (count > 0 && bound < 0 && (count == boundIndex || lastTime - times[i] >= lastPeriod))
for (int i = 1; i < times.Length - 1; i++)
{
bound = i;
lastPeriod = lastTime - times[i];
}
if (lastTime - times[i] >= lastPeriod + firstPeriod)
{
start = i;
break;
var dt1 = lastTime - times[times.Length - i];
if (dt1 <= lastPeriod)
{
x2.Add((times[times.Length - i] - firstTime).TotalSeconds);
y2.Add((double)prices[times.Length - i]);
}
else if (dt1 <= fullPeriod)
{
x1.Add((times[times.Length - i] - firstTime).TotalSeconds);
y1.Add((double)prices[times.Length - i]);
}
else
{
success = true;
break;
}
}
count++;
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;
}
success = true;
}
if (start < bound && start >= 0 && bound > 0)
{
var diff1 = prices[bound] - prices[start];
var diff2 = prices[end] - prices[bound];
res = new TwoLocalTrendsResultDto(true, diff1, diff2, start, bound, end, times[bound] - times[start], times[end] - times[bound]);
}
return res;
}
internal static bool CheckLongOpen(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{
var data = unit.GetData().Result;
var periodStat = GetTwoPeriodsProcessingData(data.timestamps, data.prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff;
return isStartOk && isEndOk && data.prices[periodStat.Start] - data.prices[periodStat.End] >= meanfullDiff;
return success;
}
internal static bool CheckLongClose(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
internal static bool TryCalcTrendDiff(DateTime[] times, decimal[] prices, out decimal diff)
{
var data = unit.GetData().Result;
var periodStat = GetTwoPeriodsProcessingData(data.timestamps, data.prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart > 0 && periodStat.DiffStart > 1.5m * meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd < meanfullDiff;
return isStartOk && isEndOk && data.prices[periodStat.End] - data.prices[periodStat.Start] >= meanfullDiff;
}
internal static TradingEvent Detect(IPriceHistoryCacheUnit data)
{
decimal meanfullDiff = 1m;
var res = TradingEvent.None;
//var downtrendStarts = data.CheckDowntrendStarting(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(7), meanfullDiff);
var uptrendStarts = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), meanfullDiff, 3);
var uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 2);
var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 5);
uptrendStarts |= downtrendEnds;
uptrendStarts |= uptrendStarts2;
if (uptrendStarts)
diff = 0;
if (times.Length <= 1 || times.Length != prices.Length)
{
res |= TradingEvent.LongClose;
return false;
}
//var downtrendEnds = data.CheckDowntrendEnding(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(15), meanfullDiff);
var uptrendEnds = data.CheckLongClose(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), meanfullDiff * 1.5m, 8);
var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 8);
uptrendEnds |= uptrendEnds2;
if (uptrendEnds)
else
{
res |= TradingEvent.LongOpen;
var startTime = times[0];
var x = new double[times.Length];
for (int i = 0; i < times.Length - 1; i++)
{
x[i] = (times[i] - startTime).TotalSeconds;
}
var line = Fit.Line(x.ToArray(), prices.Select(p => (double)p).ToArray());
var p1 = line.A + line.B * 0;
var p2 = line.A + line.B * (times[times.Length - 1] - times[0]).TotalSeconds;
diff = (decimal)(p2 - p1);
return true;
}
return res;
}
}
}

View File

@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.8" />
</ItemGroup>

View File

@ -0,0 +1,31 @@
using MathNet.Numerics;
using MathNet.Numerics.IntegralTransforms;
using System.Security.Cryptography;
namespace KLHZ.Trader.Core.Tests
{
public class FFTTests
{
[Test]
public static void Test()
{
var da = new List<float>();
for (int i = 0; i < 100; i++)
{
da.Add((float)System.Math.Sin(0.5 * i) + (float)(RandomNumberGenerator.GetInt32(0, 100)) / 300);
}
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++)
{
var d = res[i] - start[i];
}
}
}
}

View File

@ -0,0 +1,132 @@
using KLHZ.Trader.Core.Math.Declisions.Utils;
namespace KLHZ.Trader.Core.Tests
{
public class LocalTrendsTests
{
public static (DateTime[] timestamps, decimal[] prices) GetData(int bound, decimal sign = 1m)
{
var date1 = DateTime.UtcNow.AddSeconds(-2 * bound);
var val = 0m;
var timestamps = new List<DateTime>();
var prices = new List<decimal>();
var step = 0.5m;
for (int i = 0; i < 2 * bound; i++)
{
date1 = date1.AddSeconds(1);
timestamps.Add(date1);
val += step * sign;
prices.Add(val);
if (i == bound)
{
sign = sign * -1;
}
}
return (timestamps.ToArray(), prices.ToArray());
}
[Test]
public void TryGetLocalTrends1()
{
var data = GetData(50);
if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(49), TimeSpan.FromSeconds(49), 22, out var res))
{
Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.UptrendEnd);
}
else
{
Assert.Fail();
}
}
[Test]
public void TryGetLocalTrends2()
{
var data = GetData(50, -1);
if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(49), TimeSpan.FromSeconds(49), 22, out var res))
{
Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.UptrendStart);
}
else
{
Assert.Fail();
}
}
[Test]
public void TryGetLocalTrends3()
{
var data = GetData(100, -1);
if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30), 10, out var res))
{
Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.None);
}
else
{
Assert.Fail();
}
}
[Test]
public void TryGetLocalTrends4()
{
if (LocalTrends.TryGetLocalTrends(Array.Empty<DateTime>(), Array.Empty<decimal>(), TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30), 10, out var res))
{
Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.None);
}
else
{
Assert.Pass();
}
}
[Test]
public void TryGetLocalTrends5()
{
if (LocalTrends.TryGetLocalTrends(new DateTime[1] { DateTime.UtcNow }, new decimal[] { 1m }, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30), 10, out var res))
{
Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.None);
}
else
{
Assert.Pass();
}
}
[Test]
public void TryCalcTrendDiff1()
{
var data = GetData(500, -1);
if (LocalTrends.TryCalcTrendDiff(data.timestamps.Take(100).ToArray(), data.prices.Take(100).ToArray(), out var res))
{
Assert.That(res < 0);
Assert.That(System.Math.Abs(res) <= 50 && System.Math.Abs(res) > 40);
}
else
{
Assert.Fail();
}
}
[Test]
public void TryCalcTrendDiff2()
{
var data = GetData(500);
if (LocalTrends.TryCalcTrendDiff(data.timestamps.Take(100).ToArray(), data.prices.Take(100).ToArray(), out var res))
{
Assert.That(res > 0);
Assert.That(System.Math.Abs(res) <= 50 && System.Math.Abs(res) > 40);
}
else
{
Assert.Fail();
}
}
}
}

View File

@ -3,8 +3,8 @@
public static class BotModeSwitcher
{
private readonly static object _locker = new();
private static bool _canSell = true;
private static bool _canPurchase = true;
private static bool _canSell = false;
private static bool _canPurchase = false;
public static bool CanSell()
{

View File

@ -89,7 +89,20 @@ namespace KLHZ.Trader.Core.TG.Services
CommandType = TradeCommandType.MarketSell,
RecomendPrice = null,
Figi = asset.Figi,
Count = (long)asset.Count,
Count = System.Math.Abs((long)asset.Count),
EnableMargin = false,
};
await _eventBus.Broadcast(command);
}
if (asset.Count < 0)
{
var command = new TradeCommand()
{
AccountId = asset.AccountId,
CommandType = TradeCommandType.MarketBuy,
RecomendPrice = null,
Figi = asset.Figi,
Count = System.Math.Abs((long)asset.Count),
EnableMargin = false,
};
await _eventBus.Broadcast(command);