рефакторинг: вынос контрактов в отдельный проект
test / deploy_trader_prod (push) Successful in 2m42s Details

main
vlad zverzhkhovskiy 2025-08-29 12:48:44 +03:00
parent f6b98e949d
commit 2d6bc10ed8
32 changed files with 257 additions and 161 deletions

View File

@ -0,0 +1,16 @@
namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos
{
public readonly struct TradingEventsDto
{
public readonly bool LongClose;
public readonly bool LongOpen;
public TradingEventsDto(bool longClose, bool longOpen)
{
LongClose = longClose;
LongOpen = longOpen;
}
public readonly static TradingEventsDto Empty = new TradingEventsDto(false, false);
}
}

View File

@ -0,0 +1,12 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
{
public interface IPriceHistoryCacheUnit
{
public string Figi { get; }
public ValueTask AddData(INewPriceMessage priceChange);
public ValueTask<(DateTime[] timestamps, float[] prices)> GetData();
}
}

View File

@ -0,0 +1,9 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
{
public interface ITradingEventsDetector
{
public ValueTask<TradingEventsDto> Detect(IPriceHistoryCacheUnit unit);
}
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Common.Messaging.Contracts.Messages.Enums
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Enums
{
public enum TradeCommandType
{

View File

@ -0,0 +1,7 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
{
public interface IMessage
{
public string Text { get; set; }
}
}

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Common.Messaging.Contracts.Messages
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
{
public interface INewCandle
{

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Common.Messaging.Contracts.Messages
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
{
public interface INewPriceMessage
{

View File

@ -0,0 +1,9 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{
public class MessageForAdmin : IMessage
{
public required string Text { get; set; }
}
}

View File

@ -1,4 +1,6 @@
namespace KLHZ.Trader.Core.Common.Messaging.Contracts.Messages
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{
public class NewPriceMessage : INewPriceMessage
{

View File

@ -1,6 +1,6 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Enums;
namespace KLHZ.Trader.Core.Common.Messaging.Contracts.Messages
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{
public class TradeCommand
{

View File

@ -1,22 +1,17 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
using System.Threading.Channels;
namespace KLHZ.Trader.Core.Common.Messaging.Contracts
namespace KLHZ.Trader.Core.Contracts.Messaging.Interfaces
{
public interface IDataBus
{
public bool AddChannel(string key, Channel<INewPriceMessage> channel);
public bool AddChannel(string key, Channel<TradeCommand> channel);
public bool AddChannel(string key, Channel<IMessage> channel);
public bool AddChannel(string key, Channel<INewCandle> channel);
public bool AddChannel(Channel<MessageForAdmin> channel);
public Task BroadcastNewPrice(INewPriceMessage newPriceMessage);
public Task BroadcastCommand(TradeCommand command);
public Task BroadcastCommand(MessageForAdmin command);
public Task BroadcastNewCandle(INewCandle command);
}
}

View File

@ -1,5 +1,5 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Declisions.Models;
using KLHZ.Trader.Core.Declisions.Services;
namespace KLHZ.Trader.Core.Tests
{
@ -34,7 +34,7 @@ namespace KLHZ.Trader.Core.Tests
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit("", hist);
var data = cacheUnit.GetData();
var data = cacheUnit.GetData().Result;
Assert.That(data.prices.Length == count);
Assert.That(data.timestamps.Length == count);
@ -47,7 +47,7 @@ namespace KLHZ.Trader.Core.Tests
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit("", hist);
var data = cacheUnit.GetData();
var data = cacheUnit.GetData().Result;
Assert.That(data.prices.Length == count);
Assert.That(data.timestamps.Length == count);
@ -65,7 +65,7 @@ namespace KLHZ.Trader.Core.Tests
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit("", hist);
var data = cacheUnit.GetData();
var data = cacheUnit.GetData().Result;
Assert.That(data.prices.Length == count);
Assert.That(data.timestamps.Length == count);
@ -84,7 +84,7 @@ namespace KLHZ.Trader.Core.Tests
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit("", hist);
var data = cacheUnit.GetData();
var data = cacheUnit.GetData().Result;
Assert.That(data.prices.Length == count);
Assert.That(data.timestamps.Length == count);
@ -104,7 +104,7 @@ namespace KLHZ.Trader.Core.Tests
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit("", hist);
var data = cacheUnit.GetData();
var data = cacheUnit.GetData().Result;
Assert.That(data.prices.Length == count - shift);
Assert.That(data.timestamps.Length == count - shift);
@ -128,7 +128,7 @@ namespace KLHZ.Trader.Core.Tests
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit("", hist);
var data = cacheUnit.GetData();
var data = cacheUnit.GetData().Result;
Assert.That(data.prices.Length == count - shift);
Assert.That(data.timestamps.Length == count - shift);
@ -152,7 +152,7 @@ namespace KLHZ.Trader.Core.Tests
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit("", hist);
var data = cacheUnit.GetData();
var data = cacheUnit.GetData().Result;
Assert.That(data.prices.Length == count - shift);
Assert.That(data.timestamps.Length == count - shift);
@ -176,7 +176,7 @@ namespace KLHZ.Trader.Core.Tests
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit("", hist);
var data = cacheUnit.GetData();
var data = cacheUnit.GetData().Result;
Assert.That(data.prices.Length == count);
Assert.That(data.timestamps.Length == count);
@ -191,7 +191,7 @@ namespace KLHZ.Trader.Core.Tests
cacheUnit.AddData(newData1);
var data2 = cacheUnit.GetData();
var data2 = cacheUnit.GetData().Result;
Assert.IsTrue(data2.prices[data2.prices.Length - 1] == (float)newData1.Value);
Assert.IsTrue(data2.timestamps[data2.timestamps.Length - 1] == newData1.Time);
@ -199,7 +199,7 @@ namespace KLHZ.Trader.Core.Tests
cacheUnit.AddData(newData2);
var data3 = cacheUnit.GetData();
var data3 = cacheUnit.GetData().Result;
Assert.IsTrue(data3.prices[data3.prices.Length - 1] == (float)newData2.Value);
Assert.IsTrue(data3.timestamps[data3.timestamps.Length - 1] == newData2.Time);
}

View File

@ -1,5 +1,5 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Declisions.Models;
using KLHZ.Trader.Core.Declisions.Services;
using KLHZ.Trader.Core.Declisions.Utils;
namespace KLHZ.Trader.Core.Tests
@ -38,7 +38,7 @@ namespace KLHZ.Trader.Core.Tests
var startValue = 10;
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData();
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
@ -72,7 +72,7 @@ namespace KLHZ.Trader.Core.Tests
var startValue = 10;
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData();
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
@ -107,7 +107,7 @@ namespace KLHZ.Trader.Core.Tests
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData();
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
@ -142,7 +142,7 @@ namespace KLHZ.Trader.Core.Tests
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData();
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
@ -177,7 +177,7 @@ namespace KLHZ.Trader.Core.Tests
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData();
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
@ -212,7 +212,7 @@ namespace KLHZ.Trader.Core.Tests
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData();
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
@ -238,7 +238,7 @@ namespace KLHZ.Trader.Core.Tests
var unit2 = new PriceHistoryCacheUnit(figi);
var data2 = unit.GetData();
var data2 = unit.GetData().Result;
for (int i = 0; i < data2.prices.Length; i++)
{
var value = (decimal)data2.prices[i];

View File

@ -1,7 +0,0 @@
namespace KLHZ.Trader.Core.Common.Messaging.Contracts.Messages
{
public class MessageForAdmin
{
public required string Text { get; set; }
}
}

View File

@ -1,5 +1,6 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using System.Collections.Concurrent;
using System.Threading.Channels;
@ -7,14 +8,14 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
{
public class DataBus : IDataBus
{
private readonly ConcurrentDictionary<string, Channel<IMessage>> _messagesChannels = new();
private readonly ConcurrentDictionary<string, Channel<INewCandle>> _candlesChannels = new();
private readonly ConcurrentDictionary<string, Channel<INewPriceMessage>> _priceChannels = new();
private readonly ConcurrentDictionary<string, Channel<TradeCommand>> _commandChannels = new();
private readonly ConcurrentDictionary<string, Channel<MessageForAdmin>> _chatMessages = new();
public bool AddChannel(Channel<MessageForAdmin> channel)
public bool AddChannel(string key, Channel<IMessage> channel)
{
return _chatMessages.TryAdd(Guid.NewGuid().ToString(), channel);
return _messagesChannels.TryAdd(key, channel);
}
public bool AddChannel(string key, Channel<INewPriceMessage> channel)
@ -55,13 +56,5 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
await channel.Writer.WriteAsync(command);
}
}
public async Task BroadcastCommand(MessageForAdmin message)
{
foreach (var channel in _chatMessages.Values)
{
await channel.Writer.WriteAsync(message);
}
}
}
}

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices

View File

@ -1,6 +1,6 @@
namespace KLHZ.Trader.Core.Declisions.Models
namespace KLHZ.Trader.Core.Declisions.Dtos
{
public readonly struct PeriodPricesInfo
internal readonly struct PeriodPricesInfoDto
{
public readonly int Start;
public readonly int End;
@ -12,7 +12,7 @@
public readonly bool Success;
public readonly TimeSpan Period;
public PeriodPricesInfo(bool success, float firstPrice, float lastPrice, float periodDiff, float periodMin, float periodMax, TimeSpan period, int start, int end)
public PeriodPricesInfoDto(bool success, float firstPrice, float lastPrice, float periodDiff, float periodMin, float periodMax, TimeSpan period, int start, int end)
{
Success = success;
LastPrice = lastPrice;

View File

@ -1,6 +1,6 @@
namespace KLHZ.Trader.Core.Declisions.Models
namespace KLHZ.Trader.Core.Declisions.Dtos
{
public readonly struct TwoPeriodsProcessingData
internal readonly struct TwoPeriodsProcessingDto
{
public readonly int Start;
public readonly int Bound;
@ -11,7 +11,7 @@
public readonly TimeSpan PeriodStart;
public readonly TimeSpan PeriodEnd;
public TwoPeriodsProcessingData(bool success, float diffStart, float diffEnd, int start, int bound, int end,
public TwoPeriodsProcessingDto(bool success, float diffStart, float diffEnd, int start, int bound, int end,
TimeSpan periodStart, TimeSpan periodEnd)
{
Success = success;

View File

@ -1,12 +1,13 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
namespace KLHZ.Trader.Core.Declisions.Models
namespace KLHZ.Trader.Core.Declisions.Services
{
public class PriceHistoryCacheUnit
public class PriceHistoryCacheUnit : IPriceHistoryCacheUnit
{
public const int ArrayMaxLength = 500;
public readonly string Figi;
public string Figi { get; init; }
private readonly object _locker = new();
private readonly float[] Prices = new float[ArrayMaxLength];
@ -14,7 +15,7 @@ namespace KLHZ.Trader.Core.Declisions.Models
private int Length = 0;
public void AddData(INewPriceMessage priceChange)
public ValueTask AddData(INewPriceMessage priceChange)
{
lock (_locker)
{
@ -29,9 +30,10 @@ namespace KLHZ.Trader.Core.Declisions.Models
Length++;
}
}
return ValueTask.CompletedTask;
}
public (DateTime[] timestamps, float[] prices) GetData()
public ValueTask<(DateTime[] timestamps, float[] prices)> GetData()
{
var prices = new float[Length];
var timestamps = new DateTime[Length];
@ -39,7 +41,7 @@ namespace KLHZ.Trader.Core.Declisions.Models
{
Array.Copy(Prices, Prices.Length - Length, prices, 0, prices.Length);
Array.Copy(Timestamps, Prices.Length - Length, timestamps, 0, timestamps.Length);
return (timestamps, prices);
return ValueTask.FromResult((timestamps, prices));
}
}

View File

@ -1,10 +1,10 @@
using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Common.Messaging.Contracts;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages.Enums;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.Declisions.Models;
using KLHZ.Trader.Core.Declisions.Utils;
using KLHZ.Trader.Core.Exchange;
using KLHZ.Trader.Core.Exchange.Extentions;
@ -30,6 +30,8 @@ namespace KLHZ.Trader.Core.Declisions.Services
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new();
private readonly ConcurrentDictionary<string, PriceHistoryCacheUnit> _historyCash = new();
private readonly ITradingEventsDetector _tradingEventsDetector;
private readonly decimal _futureComission;
private readonly decimal _shareComission;
@ -41,6 +43,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
private readonly Channel<INewPriceMessage> _pricesChannel = Channel.CreateUnbounded<INewPriceMessage>();
public Trader(
ITradingEventsDetector tradingEventsDetector,
BotModeSwitcher botModeSwitcher,
IServiceProvider provider,
IOptions<ExchangeConfig> options,
@ -48,6 +51,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
IDbContextFactory<TraderDbContext> dbContextFactory,
InvestApiClient investApiClient)
{
_tradingEventsDetector = tradingEventsDetector;
_botModeSwitcher = botModeSwitcher;
_dataBus = dataBus;
_provider = provider;
@ -100,59 +104,11 @@ namespace KLHZ.Trader.Core.Declisions.Services
data = new PriceHistoryCacheUnit(message.Figi, message);
_historyCash.TryAdd(message.Figi, data);
}
float meanfullDiff;
if (message.Figi == "BBG004730N88")
{
meanfullDiff = 0.05f;
}
else if (message.Figi == "FUTIMOEXF000")
{
meanfullDiff = 1f;
}
else
{
continue;
}
var result = await _tradingEventsDetector.Detect(data);
try
{
//var downtrendStarts = data.CheckDowntrendStarting(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(7), meanfullDiff);
var uptrendStarts = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), meanfullDiff, 8, 3);
var uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 15, 2);
var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 15, 5);
uptrendStarts |= downtrendEnds;
uptrendStarts |= uptrendStarts2;
//var downtrendEnds = data.CheckDowntrendEnding(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(15), meanfullDiff);
var uptrendEnds = data.CheckLongClose(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), meanfullDiff * 1.5f, 8, 8);
var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 15, 8);
uptrendEnds |= uptrendEnds2;
//var uptrendEnds2 = data.CheckUptrendEnding(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(20), meanfullDiff);
//var uptrendEnds = uptrendEnds1 || uptrendEnds2;
var declisionAction = DeclisionTradeAction.Unknown;
//if (downtrendStarts)
//{
// //declisionAction = DeclisionTradeAction.OpenShort;
//}
if (uptrendStarts)
{
declisionAction = DeclisionTradeAction.OpenLong;
}
//else if (downtrendEnds)
//{
// //declisionAction = DeclisionTradeAction.CloseShort;
//}
else if (uptrendEnds)
{
declisionAction = DeclisionTradeAction.CloseLong;
}
if (declisionAction != DeclisionTradeAction.Unknown)
if (result.LongOpen)
{
using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
@ -163,7 +119,22 @@ namespace KLHZ.Trader.Core.Declisions.Services
Ticker = message.Ticker,
Price = message.Value,
Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
Action = declisionAction,
Action = DeclisionTradeAction.OpenLong,
});
await context.SaveChangesAsync();
}
if (result.LongClose)
{
using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
await context.Declisions.AddAsync(new Declision()
{
AccountId = string.Empty,
Figi = message.Figi,
Ticker = message.Ticker,
Price = message.Value,
Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
Action = DeclisionTradeAction.CloseLong,
});
await context.SaveChangesAsync();
}

View File

@ -0,0 +1,64 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.Declisions.Utils;
namespace KLHZ.Trader.Core.Declisions.Services
{
public class TradingEventsDetector : ITradingEventsDetector
{
public async ValueTask<TradingEventsDto> Detect(IPriceHistoryCacheUnit data)
{
await Task.Delay(0);
float meanfullDiff;
if (data.Figi == "BBG004730N88")
{
meanfullDiff = 0.05f;
}
else if (data.Figi == "FUTIMOEXF000")
{
meanfullDiff = 1f;
}
else
{
return TradingEventsDto.Empty;
}
//var downtrendStarts = data.CheckDowntrendStarting(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(7), meanfullDiff);
var uptrendStarts = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), meanfullDiff, 8, 3);
var uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 15, 2);
var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 15, 5);
uptrendStarts |= downtrendEnds;
uptrendStarts |= uptrendStarts2;
//var downtrendEnds = data.CheckDowntrendEnding(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(15), meanfullDiff);
var uptrendEnds = data.CheckLongClose(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), meanfullDiff * 1.5f, 8, 8);
var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 15, 8);
uptrendEnds |= uptrendEnds2;
//var uptrendEnds2 = data.CheckUptrendEnding(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(20), meanfullDiff);
//var uptrendEnds = uptrendEnds1 || uptrendEnds2;
var declisionAction = DeclisionTradeAction.Unknown;
//if (downtrendStarts)
//{
// //declisionAction = DeclisionTradeAction.OpenShort;
//}
if (uptrendStarts)
{
declisionAction = DeclisionTradeAction.OpenLong;
}
//else if (downtrendEnds)
//{
// //declisionAction = DeclisionTradeAction.CloseShort;
//}
else if (uptrendEnds)
{
declisionAction = DeclisionTradeAction.CloseLong;
}
return new TradingEventsDto(uptrendEnds, uptrendStarts);
}
}
}

View File

@ -1,13 +1,15 @@
using KLHZ.Trader.Core.Declisions.Models;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Declisions.Dtos;
using KLHZ.Trader.Core.Declisions.Services;
namespace KLHZ.Trader.Core.Declisions.Utils
{
internal static class HistoryProcessingInstruments
{
internal static PeriodPricesInfo GetPriceDiffForTimeSpan(this PriceHistoryCacheUnit unit, TimeSpan timeShift, TimeSpan timeSpan, int? pointsShift = null)
internal static PeriodPricesInfoDto GetPriceDiffForTimeSpan(this IPriceHistoryCacheUnit unit, TimeSpan timeShift, TimeSpan timeSpan, int? pointsShift = null)
{
var res = new PeriodPricesInfo(false, 0, 0, 0, 0, 0, timeSpan, 0, 0);
var data = unit.GetData();
var res = new PeriodPricesInfoDto(false, 0, 0, 0, 0, 0, timeSpan, 0, 0);
var data = unit.GetData().Result;
var times = data.timestamps;
var prices = data.prices;
if (times.Length < 2) return res;
@ -47,7 +49,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
if (intervaStartIndex >= 0 && intervaEndIndex >= 0)
{
res = new PeriodPricesInfo(
res = new PeriodPricesInfoDto(
true,
prices[intervaStartIndex],
prices[intervaEndIndex],
@ -62,31 +64,31 @@ namespace KLHZ.Trader.Core.Declisions.Utils
return res;
}
internal static bool CheckStable(this PeriodPricesInfo data, float meanfullDiff)
internal static bool CheckStable(this PeriodPricesInfoDto data, float meanfullDiff)
{
meanfullDiff = Math.Abs(meanfullDiff);
return data.Success && Math.Abs(data.PeriodDiff) < 1.5 * meanfullDiff && Math.Abs(data.PeriodMax - data.PeriodMin) < 2 * meanfullDiff;
}
internal static bool CheckGrowing(this PeriodPricesInfo data, float meanfullDiff)
internal static bool CheckGrowing(this PeriodPricesInfoDto data, float meanfullDiff)
{
return meanfullDiff > 0 && data.Success && data.PeriodDiff > meanfullDiff && Math.Abs(data.PeriodMax - data.PeriodMin) < 3 * Math.Abs(data.PeriodDiff);
}
internal static bool CheckFalling(this PeriodPricesInfo data, float meanfullDiff)
internal static bool CheckFalling(this PeriodPricesInfoDto data, float meanfullDiff)
{
meanfullDiff = -meanfullDiff;
return meanfullDiff < 0 && data.Success && data.PeriodDiff < meanfullDiff && Math.Abs(data.PeriodMax - data.PeriodMin) < 3 * Math.Abs(data.PeriodDiff);
}
internal static float CalcTrendRelationAbs(PeriodPricesInfo first, PeriodPricesInfo second)
internal static float CalcTrendRelationAbs(PeriodPricesInfoDto first, PeriodPricesInfoDto second)
{
var k1 = Math.Abs(first.PeriodDiff) / Math.Abs(first.Period.TotalSeconds);
var k2 = Math.Abs(second.PeriodDiff) / Math.Abs(second.Period.TotalSeconds);
if (k2 == 0 && k1 != 0) return 1000;
return (float)(k1 / k2);
}
internal static float CalcTrendRelationAbs(TwoPeriodsProcessingData data)
internal static float CalcTrendRelationAbs(TwoPeriodsProcessingDto data)
{
var k1 = Math.Abs(data.DiffStart) / Math.Abs(data.PeriodStart.TotalSeconds);
var k2 = Math.Abs(data.DiffEnd) / Math.Abs(data.PeriodEnd.TotalSeconds);
@ -94,7 +96,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
return (float)(k1 / k2);
}
internal static bool CheckDowntrendEnding(this PriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
internal static bool CheckDowntrendEnding(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
{
var totalDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, firstPeriod + secondPeriod);
var startDiff = unit.GetPriceDiffForTimeSpan(secondPeriod, firstPeriod);
@ -114,7 +116,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
return res;
}
internal static bool CheckUptrendEnding(this PriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
internal static bool CheckUptrendEnding(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
{
var totalDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, firstPeriod + secondPeriod);
var startDiff = unit.GetPriceDiffForTimeSpan(secondPeriod, firstPeriod);
@ -178,9 +180,9 @@ namespace KLHZ.Trader.Core.Declisions.Utils
return res;
}
internal static TwoPeriodsProcessingData GetTwoPeriodsProcessingData(this (DateTime[] timestamps, float[] prices) data, TimeSpan shift, int shiftPointsStart, int shiftPointsEnd, TimeSpan firstPeriod, float meanfullDiff)
internal static TwoPeriodsProcessingDto GetTwoPeriodsProcessingData(this (DateTime[] timestamps, float[] prices) data, TimeSpan shift, int shiftPointsStart, int shiftPointsEnd, TimeSpan firstPeriod, float meanfullDiff)
{
var res = new TwoPeriodsProcessingData(success: false, 0, 0, 0, 0, 0, TimeSpan.Zero, TimeSpan.Zero);
var res = new TwoPeriodsProcessingDto(success: false, 0, 0, 0, 0, 0, TimeSpan.Zero, TimeSpan.Zero);
var time = data.timestamps;
var prices = data.prices;
int count = -1;
@ -210,14 +212,14 @@ namespace KLHZ.Trader.Core.Declisions.Utils
{
var diff1 = prices[bound] - prices[start];
var diff2 = prices[end] - prices[bound];
res = new TwoPeriodsProcessingData(true, diff1, diff2, start, bound, end, time[bound] - time[start], time[end] - time[bound]);
res = new TwoPeriodsProcessingDto(true, diff1, diff2, start, bound, end, time[bound] - time[start], time[end] - time[bound]);
}
return res;
}
internal static bool CheckLongClose(this PriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff, int pointsStart, int pointsEnd)
internal static bool CheckLongClose(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff, int pointsStart, int pointsEnd)
{
var data = unit.GetData();
var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, meanfullDiff);
var trendRelation = CalcTrendRelationAbs(periodStat);
var isStartOk = periodStat.Success && periodStat.DiffStart > 0 && periodStat.DiffStart > 1.5 * meanfullDiff;
@ -240,9 +242,9 @@ namespace KLHZ.Trader.Core.Declisions.Utils
return isStartOk && isEndOk && (data.prices[periodStat.End] - data.prices[periodStat.Start] >= meanfullDiff);
}
internal static bool CheckUptrendStarting2(this PriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
internal static bool CheckUptrendStarting2(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
{
var data = unit.GetData();
var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, 15, 2, firstPeriod, meanfullDiff);
var trendRelation = CalcTrendRelationAbs(periodStat);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
@ -265,9 +267,9 @@ namespace KLHZ.Trader.Core.Declisions.Utils
return isStartOk && isEndOk;
}
internal static bool _CheckUptrendStarting2(this PriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
internal static bool _CheckUptrendStarting2(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
{
var data = unit.GetData();
var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, 15, 1, firstPeriod, meanfullDiff);
var trendRelation = CalcTrendRelationAbs(periodStat);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
@ -291,9 +293,9 @@ namespace KLHZ.Trader.Core.Declisions.Utils
}
internal static bool CheckLongOpen(this PriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff, int pointsStart, int pointsEnd)
internal static bool CheckLongOpen(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff, int pointsStart, int pointsEnd)
{
var data = unit.GetData();
var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, meanfullDiff);
var trendRelation = CalcTrendRelationAbs(periodStat);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;

View File

@ -1,5 +1,5 @@
using Grpc.Core;
using KLHZ.Trader.Core.Common.Messaging.Contracts;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Extentions;

View File

@ -1,6 +1,6 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Models;

View File

@ -14,4 +14,8 @@
<PackageReference Include="Tinkoff.InvestApi" Version="0.6.17" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\KLHZ.Trader.Core.Contracts\KLHZ.Trader.Core.Contracts.csproj" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,7 @@
using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Common.Messaging.Contracts;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Immutable;

View File

@ -1,5 +1,5 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Collections.Immutable;
@ -13,14 +13,14 @@ namespace KLHZ.Trader.Core.TG.Services
{
private readonly TelegramBotClient _botClient;
private readonly IUpdateHandler _updateHandler;
private readonly Channel<MessageForAdmin> _messages = Channel.CreateUnbounded<MessageForAdmin>();
private readonly Channel<IMessage> _messages = Channel.CreateUnbounded<IMessage>();
private readonly ImmutableArray<long> _admins = [];
public BotStarter(IOptions<TgBotConfig> cfg, IUpdateHandler updateHandler, IDataBus dataBus, IOptions<TgBotConfig> options)
{
_botClient = new TelegramBotClient(cfg.Value.Token);
_updateHandler = updateHandler;
dataBus.AddChannel(_messages);
dataBus.AddChannel("", _messages);
_admins = ImmutableArray.CreateRange(options.Value.Admins);
_ = ProcessMessages();
}

View File

@ -1,5 +1,5 @@
using KLHZ.Trader.Core.Common.Messaging.Contracts;
using KLHZ.Trader.Core.Common.Messaging.Contracts.Messages;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

View File

@ -1,6 +1,7 @@
using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Common.Messaging.Contracts;
using KLHZ.Trader.Core.Common.Messaging.Services;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.Declisions.Services;
using KLHZ.Trader.Core.Exchange;
@ -49,6 +50,7 @@ builder.Services.AddHostedService<Trader>();
builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>();
builder.Services.AddSingleton<BotModeSwitcher>();
builder.Services.AddSingleton<IDataBus, DataBus>();
builder.Services.AddSingleton<ITradingEventsDetector, TradingEventsDetector>();
for (int i = 0; i < 10; i++)
{

View File

@ -40,6 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deploy", "deploy", "{9DE95D
build-docker-compose.yml = build-docker-compose.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KLHZ.Trader.Core.Contracts", "KLHZ.Trader.Core.Contracts\KLHZ.Trader.Core.Contracts.csproj", "{C1ADC79B-ADDB-435D-A453-9D1623D144C4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -66,6 +68,10 @@ Global
{9BF1E4ED-CCD5-401B-9F1C-3B7625258F7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9BF1E4ED-CCD5-401B-9F1C-3B7625258F7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9BF1E4ED-CCD5-401B-9F1C-3B7625258F7E}.Release|Any CPU.Build.0 = Release|Any CPU
{C1ADC79B-ADDB-435D-A453-9D1623D144C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1ADC79B-ADDB-435D-A453-9D1623D144C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1ADC79B-ADDB-435D-A453-9D1623D144C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1ADC79B-ADDB-435D-A453-9D1623D144C4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE