diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Enums/TradeCommandType.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Enums/TradeCommandType.cs index 38991d5..9ecada1 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Enums/TradeCommandType.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Enums/TradeCommandType.cs @@ -6,5 +6,6 @@ MarketBuy = 1, MarketSell = 101, + LimitBuy = 200, } } diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/INewPrice.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/INewPrice.cs index 6528736..2b27275 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/INewPrice.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/Interfaces/INewPrice.cs @@ -7,5 +7,6 @@ public string Figi { get; set; } public string Ticker { get; set; } public DateTime Time { get; set; } + public bool IsSellPrice { get; set; } } } diff --git a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/NewPriceMessage.cs b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/NewPriceMessage.cs index fe87cdd..4a99c5f 100644 --- a/KLHZ.Trader.Core.Contracts/Messaging/Dtos/NewPriceMessage.cs +++ b/KLHZ.Trader.Core.Contracts/Messaging/Dtos/NewPriceMessage.cs @@ -1,4 +1,5 @@ using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; +using System.ComponentModel.DataAnnotations.Schema; namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos { @@ -9,5 +10,7 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos public required string Ticker { get; set; } public DateTime Time { get; set; } public bool IsHistoricalData { get; set; } + [NotMapped] + public bool IsSellPrice { get; set; } } } diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs index 029c80d..49a94f3 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs @@ -61,7 +61,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils twavbs[i2 + 1]); if (shift == 1 && !isCrossing.res) //если нет пересечения скользящих средний с окном 120 и 15 секунд между - //текущей и предыдущей точкой - можно не продолжать выполнение. + //текущей и предыдущей точкой - можно не продолжать выполнение. { break; } diff --git a/KLHZ.Trader.Core.Tests/HistoryCacheUnit2Tests.cs b/KLHZ.Trader.Core.Tests/HistoryCacheUnit2Tests.cs index 11e6709..99c8019 100644 --- a/KLHZ.Trader.Core.Tests/HistoryCacheUnit2Tests.cs +++ b/KLHZ.Trader.Core.Tests/HistoryCacheUnit2Tests.cs @@ -211,7 +211,7 @@ namespace KLHZ.Trader.Core.Tests for (int i = 0; i < 5 * PriceHistoryCacheUnit2.CacheMaxLength; i++) { cacheUnit.AddData(new PriceChange() { Figi = "", Ticker = "", Value = i, Time = DateTime.UtcNow }); - if (i >= 500) + if (i >= PriceHistoryCacheUnit2.CacheMaxLength) { var data = cacheUnit.GetData().Result; Assert.IsTrue(data.prices.Length == PriceHistoryCacheUnit2.CacheMaxLength); diff --git a/KLHZ.Trader.Core.Tests/TraderTests.cs b/KLHZ.Trader.Core.Tests/TraderTests.cs index 458bb3d..7799fd2 100644 --- a/KLHZ.Trader.Core.Tests/TraderTests.cs +++ b/KLHZ.Trader.Core.Tests/TraderTests.cs @@ -1,5 +1,6 @@ using KLHZ.Trader.Core.Common; using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; +using KLHZ.Trader.Core.Exchange.Utils; namespace KLHZ.Trader.Core.Tests { @@ -89,5 +90,11 @@ namespace KLHZ.Trader.Core.Tests }; Assert.IsTrue(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 3000, 1, 0.5m, 0.1m)); } + + [Test] + public void CalcProfitTest() + { + var profit = TradingCalculator.CaclProfit(2990, 2991.5m, 0.0025m, 10.3m, false); + } } } diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Orders/Enums/OrderbookItemType.cs b/KLHZ.Trader.Core/DataLayer/Entities/Orders/Enums/OrderbookItemType.cs index dac689c..32d52cc 100644 --- a/KLHZ.Trader.Core/DataLayer/Entities/Orders/Enums/OrderbookItemType.cs +++ b/KLHZ.Trader.Core/DataLayer/Entities/Orders/Enums/OrderbookItemType.cs @@ -9,5 +9,6 @@ BidsSummary10 = 4, AsksSummary4 = 5, BidsSummary4 = 6, + BidsAsksSummary4 = 7, } } diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Prices/PriceChange.cs b/KLHZ.Trader.Core/DataLayer/Entities/Prices/PriceChange.cs index 37f81c8..3d5be3e 100644 --- a/KLHZ.Trader.Core/DataLayer/Entities/Prices/PriceChange.cs +++ b/KLHZ.Trader.Core/DataLayer/Entities/Prices/PriceChange.cs @@ -23,5 +23,7 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices [NotMapped] public bool IsHistoricalData { get; set; } + [NotMapped] + public bool IsSellPrice { get; set; } } } diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Prices/ProcessedPrice.cs b/KLHZ.Trader.Core/DataLayer/Entities/Prices/ProcessedPrice.cs index ffcfbb6..eea0bd1 100644 --- a/KLHZ.Trader.Core/DataLayer/Entities/Prices/ProcessedPrice.cs +++ b/KLHZ.Trader.Core/DataLayer/Entities/Prices/ProcessedPrice.cs @@ -21,10 +21,12 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices [Column("ticker")] public required string Ticker { get; set; } - [NotMapped] public bool IsHistoricalData { get; set; } [Column("processor")] public required string Processor { get; set; } + + [NotMapped] + public bool IsSellPrice { get; set; } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs b/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs index 7bf1860..8702e75 100644 --- a/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Tinkoff.InvestApi; using Tinkoff.InvestApi.V1; -using static Google.Rpc.Context.AttributeContext.Types; namespace KLHZ.Trader.Core.Exchange.Services { @@ -28,6 +27,8 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly CancellationTokenSource _cts = new(); private readonly IDataBus _eventBus; private readonly bool _exchangeDataRecievingEnabled; + private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); + public ExchangeDataReader(InvestApiClient investApiClient, IDataBus eventBus, TraderDataProvider tradeDataProvider, IOptions options, IDbContextFactory dbContextFactory, ILogger logger) @@ -72,28 +73,30 @@ namespace KLHZ.Trader.Core.Exchange.Services private async Task SubscribeMyTrades() { - var req = new TradesStreamRequest(); + var req = new PortfolioStreamRequest(); foreach (var a in _tradeDataProvider.Accounts) { req.Accounts.Add(a.Key); } - using var stream = _investApiClient.OrdersStream.TradesStream(req); + + using var stream = _investApiClient.OperationsStream.PortfolioStream(req); await foreach (var response in stream.ResponseStream.ReadAllAsync()) { - if (response.OrderTrades != null) + if (response.Portfolio?.Positions != null) { - foreach(var t in response.OrderTrades.Trades) + if (_semaphoreSlim.CurrentCount == 1) { - //await _tradeDataProvider.LogDeal(new Models.AssetsAccounting.DealResult() - //{ - // AccountId = response.OrderTrades.AccountId, - // Figi = response.OrderTrades.Figi, - // Count = t.Quantity, - // Price = t.Price, - // Direction = Models.AssetsAccounting.DealDirection - //}) + try + { + await _semaphoreSlim.WaitAsync(TimeSpan.FromSeconds(5)); + await _tradeDataProvider.SyncPortfolio(response.Portfolio.AccountId); + } + catch (Exception ex) + { + + } + _semaphoreSlim.Release(); } - } } } @@ -152,6 +155,7 @@ namespace KLHZ.Trader.Core.Exchange.Services Time = response.Trade.Time.ToDateTime().ToUniversalTime(), Value = response.Trade.Price, IsHistoricalData = false, + IsSellPrice = response.Trade.Direction == TradeDirection.Sell }; await _eventBus.Broadcast(message); diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index ea9291f..f5d9fb6 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -118,7 +118,7 @@ namespace KLHZ.Trader.Core.Exchange.Services var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); foreach (var acc in accounts) { - var assets = acc.Assets.Values.Where(a => a.Figi == message.Figi && DateTime.UtcNow - a.BoughtAt > TimeSpan.FromHours(4)).ToArray(); + var assets = acc.Assets.Values.Where(a => a.Figi == message.Figi && (DateTime.UtcNow - a.BoughtAt > TimeSpan.FromHours(4))).ToArray(); foreach (var asset in assets) { var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, diff --git a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs index 10c9615..c4a53f1 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs @@ -17,6 +17,7 @@ using Tinkoff.InvestApi; using Tinkoff.InvestApi.V1; using Asset = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.Asset; using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.AssetType; +using PositionType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.PositionType; namespace KLHZ.Trader.Core.Exchange.Services { @@ -181,6 +182,14 @@ namespace KLHZ.Trader.Core.Exchange.Services return _assetTypesCache.TryGetValue(figi, out var t) ? t : AssetType.Unknown; } + internal async Task SyncPortfolio(string accountId) + { + if (Accounts.TryGetValue(accountId, out var account)) + { + await SyncPortfolio(account); + } + } + internal async Task SyncPortfolio(ManagedAccount account) { try @@ -231,6 +240,24 @@ namespace KLHZ.Trader.Core.Exchange.Services #pragma warning restore CS0612 // Тип или член устарел account.Assets.AddOrUpdate(asset.Figi, asset, (k, v) => asset); oldAssets.Remove(asset.Figi); + if (trade == null && position.InstrumentType != "currency") + { + trade = new DataLayer.Entities.Trades.Trade() + { + AccountId = account.AccountId, + Figi = position.Figi, + Ticker = position.Ticker, + ArchiveStatus = 0, + Asset = (DataLayer.Entities.Trades.Enums.AssetType)(int)GetAssetTypeByFigi(position.Figi), + BoughtAt = DateTime.UtcNow, + Count = position.Quantity, + Direction = position.Quantity > 0 ? DataLayer.Entities.Trades.Enums.TradeDirection.Buy : DataLayer.Entities.Trades.Enums.TradeDirection.Sell, + Position = position.Quantity > 0 ? DataLayer.Entities.Trades.Enums.PositionType.Long : DataLayer.Entities.Trades.Enums.PositionType.Short, + Price = price + }; + await context.Trades.AddAsync(trade); + await context.SaveChangesAsync(); + } } account.Total = portfolio.TotalAmountPortfolio; diff --git a/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs b/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs index 1244475..d5e872d 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TradingCommandsExecutor.cs @@ -1,6 +1,5 @@ using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; -using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Threading.Channels; @@ -42,18 +41,21 @@ namespace KLHZ.Trader.Core.Exchange.Services try { var dir = OrderDirection.Unspecified; - var dealDirection = DealDirection.Unknown; - var sign = 1; + var orderType = OrderType.Unspecified; if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy) { dir = OrderDirection.Buy; - dealDirection = DealDirection.Buy; + orderType = OrderType.Market; } else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell) { - sign = -1; dir = OrderDirection.Sell; - dealDirection = DealDirection.Sell; + orderType = OrderType.Market; + } + else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitBuy && tradeCommand.RecomendPrice.HasValue) + { + dir = OrderDirection.Buy; + orderType = OrderType.Limit; } var req = new PostOrderRequest() @@ -61,23 +63,24 @@ namespace KLHZ.Trader.Core.Exchange.Services AccountId = tradeCommand.AccountId, InstrumentId = tradeCommand.Figi, Direction = dir, - OrderType = OrderType.Market, + Price = tradeCommand.RecomendPrice ?? 0, + OrderType = orderType, Quantity = tradeCommand.Count, ConfirmMarginTrade = tradeCommand.EnableMargin, }; var res = await _investApiClient.Orders.PostOrderAsync(req); - var result = new DealResult - { - Count = sign * res.LotsExecuted, - Price = res.ExecutedOrderPrice, - Success = true, - Direction = dealDirection, - AccountId = tradeCommand.AccountId, - Figi = tradeCommand.Figi, - }; - await _tradeDataProvider.LogDeal(result); + //var result = new DealResult + //{ + // Count = sign * res.LotsExecuted, + // Price = res.ExecutedOrderPrice, + // Success = true, + // Direction = dealDirection, + // AccountId = tradeCommand.AccountId, + // Figi = tradeCommand.Figi, + //}; + //await _tradeDataProvider.LogDeal(result); } catch (Exception ex) {