Compare commits

...

2 Commits

Author SHA1 Message Date
vlad zverzhkhovskiy 80221648f7 Добавлено логирование сделок
test / deploy_trader_prod (push) Successful in 1m31s Details
2025-09-15 13:51:17 +03:00
vlad zverzhkhovskiy ff22dd0a9c Добавлено логирование сделок 2025-09-15 13:48:00 +03:00
10 changed files with 335 additions and 44 deletions

View File

@ -4,6 +4,7 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{
public interface ITradeCommand
{
public Guid CommandId { get; }
public TradeCommandType CommandType { get; }
public string Figi { get; }
public decimal? RecomendPrice { get; }

View File

@ -5,6 +5,7 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{
public class TradeCommand : ITradeCommand
{
public Guid CommandId { get; init; } = Guid.NewGuid();
public TradeCommandType CommandType { get; init; }
public required string Figi { get; init; }
public decimal? RecomendPrice { get; init; }

View File

@ -10,7 +10,28 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
var da = new List<float>();
for (int i = 0; i < 1000; i++)
{
da.Add((float)System.Math.Sin(0.01 * i)+ (float)System.Math.Cos(0.01 * i));
da.Add((float)System.Math.Sin(0.01 * i) + (float)System.Math.Cos(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];
}
}
public static void А()
{
var da = new List<float>();
for (int i = 0; i < 1000; i++)
{
da.Add((float)System.Math.Sin(0.01 * i) + (float)System.Math.Cos(0.01 * i));
}
var start = da.ToArray();
@ -28,9 +49,9 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
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;
return TimeSpan.FromSeconds(1/fharm);
return TimeSpan.FromSeconds(1 / fharm);
}
public static double CalcCurrentPhase(Complex32[] spectr, int harmonyNumber)
@ -39,32 +60,89 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
return System.Math.Atan(item.Imaginary / item.Real);
}
public static (double min, double max) CalcPhaseRange(double aSin, double aCos, double initPhase, double maxLevel)
public static (double min, double max) CalcPhaseRangeFoxMax(double aSin, double aCos, double initPhase, double level)
{
return CalcPhaseRange(aSin, aCos,initPhase, level, CheckMaxValue);
}
public 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)
{
var x = new List<double>();
var x2 = new List<double>();
var xIndexes = new List<int>();
var y = new List<double>();
for (double i = 0; i <= 10 * System.Math.PI; i += 0.0001)
var max = double.MinValue;
var min = double.MaxValue;
var _2pi = 2 * System.Math.PI;
for (double i = 0; i <= 10 * System.Math.PI; i += 0.01)
{
var _2pi = 2 * System.Math.PI;
var df = ((i / _2pi) % 1) * _2pi;
var df = (((i) / _2pi) % 1) * _2pi;
var val = aSin * System.Math.Sin(i + initPhase) + aCos * System.Math.Cos(i + initPhase);
if (val > max)
{
max = val;
}
if (val < min)
{
min = val;
}
x.Add(df);
y.Add(aSin * System.Math.Sin(i + initPhase) + aCos * System.Math.Cos(i + initPhase));
y.Add(val);
}
var prevI = -1;
var max = y.Max();
int start = -2;
int prevI = -2;
int end = -2;
var drange = -2;
var minPhaseTmp = 0d;
var maxPhaseTmp = 0d;
var minPhase = 0d;
var maxPhase = 0d;
for (int i = 0; i < y.Count; i++)
{
if (y[i] > (1 - maxLevel) * max && (i - prevI == 1 || prevI < 0))
if (comparer(y[i],min,max,level))
{
x2.Add(x[i]);
prevI = i;
if (start < 0)
{
minPhaseTmp = x[i];
start = i;
}
}
else if (start >= 0 && i - prevI == 1)
{
end = prevI;
maxPhaseTmp = x[end];
var drangeTmp = end - start;
if (drangeTmp > drange)
{
drange = drangeTmp;
minPhase = minPhaseTmp;
maxPhase = maxPhaseTmp;
}
start = -2;
}
prevI = i;
}
var minPhase = x2.Min();
var maxPhase = x2.Max();
return (minPhase, maxPhase);
}
internal static bool CheckMaxValue(double val, double min, double max, double relativeValue)
{
return (val > (1 - relativeValue) * max);
}
internal static bool CheckMinValue(double val, double min, double max, double relativeValue)
{
return (val < (1 - relativeValue) * min);
}
}
}

View File

@ -0,0 +1,57 @@
namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
public static class SignalProcessing
{
public static (DateTime[], double[]) InterpolateData(DateTime[] timestamps, double[] values, TimeSpan timeStep)
{
var res = new List<double>();
var res2 = new List<DateTime>();
var startTime = new DateTime(timestamps[0].Year, timestamps[0].Month, timestamps[0].Day, 0, 0, 0, DateTimeKind.Utc);
var dt = timestamps[0] - startTime;
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) ;
var firstBound = startTime;
var secondBound = startTime + timeStep;
var bound = 0;
for (int i = 0; i < totalSteps; i++)
{
var count = 0;
var sum = 0d;
for (int i1 = bound; i1 < timestamps.Length; i1++)
{
if (timestamps[i1]> firstBound && timestamps[i1] <= secondBound)
{
count++;
sum += values[i1];
}
else if (count != 0)
{
bound = i1;
break;
}
}
if (count != 0)
{
res.Add(sum / count);
res2.Add(secondBound);
}
else if (bound< timestamps.Length-2)
{
res.Add(res.Last());
res2.Add(secondBound);
}
firstBound += timeStep;
secondBound += timeStep;
}
return (res2.ToArray(), res.ToArray());
}
}
}

View File

@ -10,24 +10,24 @@ namespace KLHZ.Trader.Core.Tests
[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)System.Math.Cos(0.5 * i));
}
//var da = new List<float>();
//for (int i = 0; i < 100; i++)
//{
// da.Add((float)System.Math.Sin(0.5 * i)+(float)System.Math.Cos(0.5 * i));
//}
var start = da.ToArray();
var arrv = da.Select(d => new Complex32(d, 0)).ToArray();
Fourier.Forward(arrv);
var tem = arrv.Select(a => Complex32.Abs(a)).ToArray();
var s = tem.Sum();
Fourier.Inverse(arrv);
var res = arrv.Select(a => a.Real).ToArray();
//var start = da.ToArray();
//var arrv = da.Select(d => new Complex32(d, 0)).ToArray();
//Fourier.Forward(arrv);
//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];
}
//for (int i = 0; i < 1000; i++)
//{
// var d = res[i] - start[i];
//}
}
[Test]
@ -41,9 +41,69 @@ namespace KLHZ.Trader.Core.Tests
[Test]
public static void CalcPhaseRange()
public static void CalcPhaseRangeForMax_Sin()
{
var res = FFT.CalcPhaseRange(1, 0, 0, 0.001);
var res = FFT.CalcPhaseRangeFoxMax(1, 0, 0, 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);
Assert.IsTrue(res.max < System.Math.PI / 2 * 1.1);
}
[Test]
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);
}
[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);
}
[Test]
public static void CalcPhaseRangeForMax_Cos()
{
var res = FFT.CalcPhaseRangeFoxMax(0, 1, 0, 0.001);
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);
}
[Test]
public static void CalcPhaseRangeForMin_Cos()
{
var res = FFT.CalcPhaseRangeFoxMin(0, 1, 0, 0.001);
Assert.IsTrue(res.min < System.Math.PI);
Assert.IsTrue(res.min > System.Math.PI * 0.9);
Assert.IsTrue(res.max > System.Math.PI);
Assert.IsTrue(res.max < System.Math.PI * 1.1);
}
[Test]
public static void CalcPhaseRangeForMax_CosWithShift()
{
var res = FFT.CalcPhaseRangeFoxMax(0, 1, System.Math.PI / 2, 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);
}
[Test]
public static void CalcPhaseRangeForMax_CosWithShift2()
{
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

@ -0,0 +1,29 @@
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
{
public class SignalProcessingTests
{
[Test]
public static void Test()
{
var da = new List<double>();
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);
times.Add(startDt);
da.Add((float)System.Math.Sin(0.01 * i) + (float)System.Math.Cos(0.01 * i));
}
var res = SignalProcessing.InterpolateData(times.ToArray(), da.ToArray(), TimeSpan.FromSeconds(5));
}
}
}

View File

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

View File

@ -19,6 +19,7 @@ using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Threading.Channels;
using Tinkoff.InvestApi;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.AssetType;
namespace KLHZ.Trader.Core.Exchange.Services
@ -125,7 +126,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
var stoppingKey = message.Figi + asset.AccountId;
if (message.Time - asset.BoughtAt > TimeSpan.FromMinutes(4) && profit < -66m && !ClosingStops.ContainsKey(stoppingKey))
{
await _dataBus.Broadcast(new TradeCommand()
var command = new TradeCommand()
{
AccountId = asset.AccountId,
Figi = message.Figi,
@ -134,7 +135,10 @@ namespace KLHZ.Trader.Core.Exchange.Services
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);
OpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(10);
ClosingStops[stoppingKey] = DateTime.UtcNow.AddSeconds(30);
await LogDeclision(DeclisionTradeAction.CloseLong, message, profit);
@ -143,7 +147,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
if (message.Time - asset.BoughtAt > TimeSpan.FromHours(4) && profit > 100 && !ClosingStops.ContainsKey(stoppingKey))
{
await _dataBus.Broadcast(new TradeCommand()
var command = new TradeCommand()
{
AccountId = asset.AccountId,
Figi = message.Figi,
@ -152,7 +156,10 @@ namespace KLHZ.Trader.Core.Exchange.Services
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);
@ -211,14 +218,17 @@ namespace KLHZ.Trader.Core.Exchange.Services
{
if (RandomNumberGenerator.GetInt32(100) > 50)
{
await _dataBus.Broadcast(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);
@ -261,7 +271,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
if (profit > 0 && !ClosingStops.ContainsKey(stoppingKey))
{
ClosingStops[stoppingKey] = DateTime.UtcNow.AddSeconds(30);
await _dataBus.Broadcast(new TradeCommand()
var command = new TradeCommand()
{
AccountId = asset.AccountId,
Figi = message.Figi,
@ -269,7 +279,10 @@ namespace KLHZ.Trader.Core.Exchange.Services
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);
if (loggedDeclisions == 0)
{
loggedDeclisions++;

View File

@ -3,8 +3,10 @@ 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
{
@ -69,8 +71,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
ConfirmMarginTrade = tradeCommand.EnableMargin,
};
_logger.LogWarning("Получена команда c id {commandId} на операцию с активом {figi}! Тип заявки сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
tradeCommand.CommandId, req.InstrumentId, req.OrderType, req.Quantity, req.ConfirmMarginTrade);
var res = await _investApiClient.Orders.PostOrderAsync(req);
_logger.LogWarning("Исполнена команда c id {commandId} на операцию с активом {figi}! Направление: {dir}; Число лотов: {lots}; цена: {price}", tradeCommand.CommandId, res.Figi,
res.Direction, res.LotsExecuted, (decimal)res.ExecutedOrderPrice);
//var result = new DealResult
//{
// Count = sign * res.LotsExecuted,

View File

@ -4,8 +4,10 @@ using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Orders;
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
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
{
@ -200,6 +202,49 @@ namespace KLHZ.Trader.Service.Controllers
}
[HttpGet]
public async Task TestInmterpolate(string figi)
{
try
{
var t1 = DateTime.UtcNow.AddHours(-4);
var t2 = DateTime.UtcNow.AddHours(1);
//t1 = new DateTime(2025, 9, 15, 10, 1, 0, DateTimeKind.Utc);
//t2 = new DateTime(2025, 9, 15, 10, 1, 50, DateTimeKind.Utc);
using var context1 = await _dbContextFactory.CreateDbContextAsync();
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var data = await context1.PriceChanges
.Where(i => i.Time >= t1 && i.Time <= t2 && i.Figi == figi)
.OrderBy(i => i.Time)
.ToArrayAsync();
var buffer = new List<ProcessedPrice>();
var res = SignalProcessing.InterpolateData(data.Select(d => d.Time).ToArray(), data.Select(d => (double)d.Value).ToArray(),
TimeSpan.FromSeconds(1));
for (int i=0;i< res.Item1.Length; i++)
{
buffer.Add(new ProcessedPrice()
{
Figi = figi,
Processor = "1secinterpol",
Ticker = data[0].Ticker,
Count = 1,
Direction = 0,
Time = res.Item1[i],
Value =(decimal) res.Item2[i]
});
}
await context1.ProcessedPrices.AddRangeAsync(buffer);
await context1.SaveChangesAsync();
}
catch (Exception ex)
{
}
}
//[HttpGet]
//public async Task GetBalance(string figi)
//{