From 7cc13d9ba089b21ef7d190d922276c69efaa26c6 Mon Sep 17 00:00:00 2001 From: vlad zverzhkhovskiy Date: Tue, 9 Sep 2025 02:10:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D1=80=D0=B0=D1=82=D0=B5=D0=B3?= =?UTF-8?q?=D0=B8=D0=B8=20+=20=D1=84=D0=B8=D0=BA=D1=81=20=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=D0=B4=D1=8B=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20IMOEXF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dtos/Interfaces/ITradeCommand.cs | 1 + .../Messaging/Dtos/TradeCommand.cs | 1 + .../Declisions/Utils/MovingAverage.cs | 46 +++++-- KLHZ.Trader.Core/Exchange/Services/Trader.cs | 115 +----------------- .../Exchange/Services/TraderDataProvider.cs | 9 +- .../Services/TradingCommandsExecutor.cs | 2 +- .../TG/Services/BotMessagesHandler.cs | 30 +++-- 7 files changed, 70 insertions(+), 134 deletions(-) diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ITradeCommand.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ITradeCommand.cs index f34b63e..d68633a 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ITradeCommand.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/ITradeCommand.cs @@ -9,5 +9,6 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces public decimal? RecomendPrice { get; } public long Count { get; } public string AccountId { get; } + public bool EnableMargin { get; } } } diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/TradeCommand.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/TradeCommand.cs index 3530952..4999d40 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/TradeCommand.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/TradeCommand.cs @@ -10,5 +10,6 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos public decimal? RecomendPrice { get; init; } public long Count { get; init; } public required string AccountId { get; init; } + public bool EnableMargin { get; init; } = true; } } diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs index cbe9274..e7458dd 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs @@ -71,9 +71,44 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils if (isCrossing.res) { crossings.Add(i2); + if (crossings.Count == 4 || (shift + 1 == size - 1 || shift + 1 == prices.Length - 1)) + { + if ((shift + 1 == size - 1 || shift + 1 == prices.Length - 1)) + { + crossings.Add(shift); + } + var diffTotal = pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]]; + for (int crossingShift = 1; crossingShift < crossings.Count - 2; crossingShift++) + { + var diff = pricesForFinalComparison[crossings[crossingShift]] - pricesForFinalComparison[crossings[crossingShift + 1]]; + if (diff >= 0) + { + diffTotal += diff; + } + else + { + break; + } + } + + // если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта + if (twavss[size - 1] <= twavbs[size - 1] && twavss[size - 2] > twavbs[size - 2]) + { + if (diffTotal >= meanfullStep + && times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart) + { + res |= TradingEvent.UptrendEnd; + } + break; + } + } if (crossings.Count == 2 || (shift + 1 == size - 1 || shift + 1 == prices.Length - 1)) { + if ((shift + 1 == size - 1 || shift + 1 == prices.Length - 1)) + { + crossings.Add(shift); + } // если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга и закрытия шорта if (twavss[size - 1] >= twavbs[size - 1] && twavss[size - 2] < twavbs[size - 2]) { @@ -84,17 +119,6 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils } break; } - - // если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта - if (twavss[size - 1] <= twavbs[size - 1] && twavss[size - 2] > twavbs[size - 2]) - { - if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] >= meanfullStep - && times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart) - { - res |= TradingEvent.UptrendEnd; - } - break; - } } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index dfc8927..09e0370 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -28,8 +28,6 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly IDataBus _dataBus; private readonly TraderDataProvider _tradeDataProvider; private readonly ILogger _logger; - private readonly ConcurrentDictionary DeferredLongOpens = new(); - private readonly ConcurrentDictionary DeferredLongCloses = new(); private readonly ConcurrentDictionary OpeningStops = new(); private readonly ConcurrentDictionary Leverages = new(); @@ -131,7 +129,8 @@ namespace KLHZ.Trader.Core.Exchange.Services { AccountId = asset.AccountId, Figi = message.Figi, - CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, + CommandType = asset.Count < 0? Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy + : Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, Count = (long)asset.Count, RecomendPrice = null, }); @@ -146,7 +145,7 @@ namespace KLHZ.Trader.Core.Exchange.Services INewPrice message, int windowMaxSize) { var res = TradingEvent.None; - var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 25, 120, TimeSpan.FromSeconds(20), 1m); + var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 25, 120, TimeSpan.FromSeconds(20), 1.5m); //var resultLongClose = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 15, 120, 1.5m).events; //ar uptrendStarts = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(20), 1.5m, 15); @@ -282,114 +281,6 @@ namespace KLHZ.Trader.Core.Exchange.Services } } - private async Task ProcessDeferredLongOpens(INewPrice message, DateTime currentTime) - { - if (message.Figi == "FUTIMOEXF000") - { - DeferredTrade? longOpen; - DeferredLongOpens.TryGetValue(message.Figi, out longOpen); - if (longOpen != null) - { - var t = currentTime; - if (longOpen.Time <= t - && t - longOpen.Time < TimeSpan.FromMinutes(3)) - { - DeferredLongOpens.TryRemove(message.Figi, out _); - if (message.Value - longOpen.Price < 1) - { - if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) - { - var accounts = _tradeDataProvider.Accounts - .Where(a => !a.Value.Assets.ContainsKey(message.Figi)) - .ToArray(); - foreach (var acc in accounts) - { - if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart)) - { - if (RandomNumberGenerator.GetInt32(100) > 50) - { - await _dataBus.Broadcast(new TradeCommand() - { - AccountId = acc.Value.AccountId, - Figi = message.Figi, - CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy, - Count = 1, - RecomendPrice = null, - }); - } - - await LogDeclision(DeclisionTradeAction.OpenLong, message); - } - } - } - else - { - await LogDeclision(DeclisionTradeAction.OpenLong, message); - } - } - } - } - } - } - - private async Task ProcessDeferredLongCloses(INewPrice message, DateTime currentTime) - { - if (message.Figi == "FUTIMOEXF000") - { - DeferredTrade? longClose; - DeferredLongCloses.TryGetValue(message.Figi, out longClose); - if (longClose != null) - { - if (longClose.Time <= currentTime) - { - DeferredLongCloses.TryRemove(message.Figi, out _); - if (longClose.Price - message.Value < 1) - { - var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); - if (!message.IsHistoricalData && BotModeSwitcher.CanSell()) - { - 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.Common && asset.Count > 0) - { - profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, - GetComission(assetType), 1, false); - } - 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) - { - await _dataBus.Broadcast(new TradeCommand() - { - AccountId = asset.AccountId, - Figi = message.Figi, - CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, - Count = (long)asset.Count, - RecomendPrice = null, - }); - await LogDeclision(DeclisionTradeAction.CloseLong, message, profit); - } - } - } - else - { - await LogDeclision(DeclisionTradeAction.CloseLong, message); - } - } - } - } - } - } - private async Task LogPrice(INewPrice message, string processor, decimal value) { await _tradeDataProvider.LogPrice(new ProcessedPrice() diff --git a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs index 94a8ae1..eb2429f 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs @@ -1,4 +1,5 @@ -using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; +using Google.Protobuf.WellKnownTypes; +using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.DataLayer; @@ -416,5 +417,11 @@ namespace KLHZ.Trader.Core.Exchange.Services } } } + + public ValueTask GetAssetsByFigi(string figi) + { + var assets = Accounts.Values.SelectMany(a => a.Assets.Values.Where(aa => aa.Figi == figi)).ToArray(); + return ValueTask.FromResult(assets); + } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs b/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs index e7febaf..1244475 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs @@ -63,7 +63,7 @@ namespace KLHZ.Trader.Core.Exchange.Services Direction = dir, OrderType = OrderType.Market, Quantity = tradeCommand.Count, - ConfirmMarginTrade = true, + ConfirmMarginTrade = tradeCommand.EnableMargin, }; var res = await _investApiClient.Orders.PostOrderAsync(req); diff --git a/KLHZ.Trader.Core/TG/Services/BotMessagesHandler.cs b/KLHZ.Trader.Core/TG/Services/BotMessagesHandler.cs index ee64c65..f08a802 100644 --- a/KLHZ.Trader.Core/TG/Services/BotMessagesHandler.cs +++ b/KLHZ.Trader.Core/TG/Services/BotMessagesHandler.cs @@ -2,6 +2,7 @@ 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.Exchange.Services; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Immutable; @@ -17,8 +18,10 @@ namespace KLHZ.Trader.Core.TG.Services private readonly ImmutableArray _admins = []; private readonly IDataBus _eventBus; private readonly ILogger _logger; - public BotMessagesHandler(IDataBus eventBus, IOptions options, ILogger logger) + private readonly TraderDataProvider _traderDataProvider; + public BotMessagesHandler(IDataBus eventBus, IOptions options, ILogger logger, TraderDataProvider traderDataProvider) { + _traderDataProvider = traderDataProvider; _logger = logger; _eventBus = eventBus; _admins = ImmutableArray.CreateRange(options.Value.Admins); @@ -74,15 +77,24 @@ namespace KLHZ.Trader.Core.TG.Services } case "продать IMOEXF": { - var command = new TradeCommand() + + var assets = await _traderDataProvider.GetAssetsByFigi("FUTIMOEXF000"); + foreach(var asset in assets) { - AccountId = "2274189208", - CommandType = TradeCommandType.MarketSell, - RecomendPrice = null, - Figi = "FUTIMOEXF000", - Count = 1, - }; - await _eventBus.Broadcast(command); + if (asset.Count > 0) + { + var command = new TradeCommand() + { + AccountId = asset.AccountId, + CommandType = TradeCommandType.MarketSell, + RecomendPrice = null, + Figi = asset.Figi, + Count = (long)asset.Count, + }; + await _eventBus.Broadcast(command); + } + } + break; } case "купить IMOEXF":