обновление логики + обновление механизма логирования сделок
test / deploy_trader_prod (push) Successful in 7m30s Details

dev
vlad zverzhkhovskiy 2025-09-09 13:48:52 +03:00
parent c2105ad019
commit 50766b6f20
13 changed files with 86 additions and 35 deletions

View File

@ -6,5 +6,6 @@
MarketBuy = 1, MarketBuy = 1,
MarketSell = 101, MarketSell = 101,
LimitBuy = 200,
} }
} }

View File

@ -7,5 +7,6 @@
public string Figi { get; set; } public string Figi { get; set; }
public string Ticker { get; set; } public string Ticker { get; set; }
public DateTime Time { get; set; } public DateTime Time { get; set; }
public bool IsSellPrice { get; set; }
} }
} }

View File

@ -1,4 +1,5 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos 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 required string Ticker { get; set; }
public DateTime Time { get; set; } public DateTime Time { get; set; }
public bool IsHistoricalData { get; set; } public bool IsHistoricalData { get; set; }
[NotMapped]
public bool IsSellPrice { get; set; }
} }
} }

View File

@ -61,7 +61,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
twavbs[i2 + 1]); twavbs[i2 + 1]);
if (shift == 1 && !isCrossing.res) //если нет пересечения скользящих средний с окном 120 и 15 секунд между if (shift == 1 && !isCrossing.res) //если нет пересечения скользящих средний с окном 120 и 15 секунд между
//текущей и предыдущей точкой - можно не продолжать выполнение. //текущей и предыдущей точкой - можно не продолжать выполнение.
{ {
break; break;
} }

View File

@ -211,7 +211,7 @@ namespace KLHZ.Trader.Core.Tests
for (int i = 0; i < 5 * PriceHistoryCacheUnit2.CacheMaxLength; i++) for (int i = 0; i < 5 * PriceHistoryCacheUnit2.CacheMaxLength; i++)
{ {
cacheUnit.AddData(new PriceChange() { Figi = "", Ticker = "", Value = i, Time = DateTime.UtcNow }); cacheUnit.AddData(new PriceChange() { Figi = "", Ticker = "", Value = i, Time = DateTime.UtcNow });
if (i >= 500) if (i >= PriceHistoryCacheUnit2.CacheMaxLength)
{ {
var data = cacheUnit.GetData().Result; var data = cacheUnit.GetData().Result;
Assert.IsTrue(data.prices.Length == PriceHistoryCacheUnit2.CacheMaxLength); Assert.IsTrue(data.prices.Length == PriceHistoryCacheUnit2.CacheMaxLength);

View File

@ -1,5 +1,6 @@
using KLHZ.Trader.Core.Common; using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using KLHZ.Trader.Core.Exchange.Utils;
namespace KLHZ.Trader.Core.Tests 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)); 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);
}
} }
} }

View File

@ -9,5 +9,6 @@
BidsSummary10 = 4, BidsSummary10 = 4,
AsksSummary4 = 5, AsksSummary4 = 5,
BidsSummary4 = 6, BidsSummary4 = 6,
BidsAsksSummary4 = 7,
} }
} }

View File

@ -23,5 +23,7 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
[NotMapped] [NotMapped]
public bool IsHistoricalData { get; set; } public bool IsHistoricalData { get; set; }
[NotMapped]
public bool IsSellPrice { get; set; }
} }
} }

View File

@ -21,10 +21,12 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
[Column("ticker")] [Column("ticker")]
public required string Ticker { get; set; } public required string Ticker { get; set; }
[NotMapped]
public bool IsHistoricalData { get; set; } public bool IsHistoricalData { get; set; }
[Column("processor")] [Column("processor")]
public required string Processor { get; set; } public required string Processor { get; set; }
[NotMapped]
public bool IsSellPrice { get; set; }
} }
} }

View File

@ -13,7 +13,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Tinkoff.InvestApi; using Tinkoff.InvestApi;
using Tinkoff.InvestApi.V1; using Tinkoff.InvestApi.V1;
using static Google.Rpc.Context.AttributeContext.Types;
namespace KLHZ.Trader.Core.Exchange.Services namespace KLHZ.Trader.Core.Exchange.Services
{ {
@ -28,6 +27,8 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private readonly IDataBus _eventBus; private readonly IDataBus _eventBus;
private readonly bool _exchangeDataRecievingEnabled; private readonly bool _exchangeDataRecievingEnabled;
private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
public ExchangeDataReader(InvestApiClient investApiClient, IDataBus eventBus, TraderDataProvider tradeDataProvider, public ExchangeDataReader(InvestApiClient investApiClient, IDataBus eventBus, TraderDataProvider tradeDataProvider,
IOptions<ExchangeConfig> options, IDbContextFactory<TraderDbContext> dbContextFactory, IOptions<ExchangeConfig> options, IDbContextFactory<TraderDbContext> dbContextFactory,
ILogger<ExchangeDataReader> logger) ILogger<ExchangeDataReader> logger)
@ -72,28 +73,30 @@ namespace KLHZ.Trader.Core.Exchange.Services
private async Task SubscribeMyTrades() private async Task SubscribeMyTrades()
{ {
var req = new TradesStreamRequest(); var req = new PortfolioStreamRequest();
foreach (var a in _tradeDataProvider.Accounts) foreach (var a in _tradeDataProvider.Accounts)
{ {
req.Accounts.Add(a.Key); 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()) 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() try
//{ {
// AccountId = response.OrderTrades.AccountId, await _semaphoreSlim.WaitAsync(TimeSpan.FromSeconds(5));
// Figi = response.OrderTrades.Figi, await _tradeDataProvider.SyncPortfolio(response.Portfolio.AccountId);
// Count = t.Quantity, }
// Price = t.Price, catch (Exception ex)
// Direction = Models.AssetsAccounting.DealDirection {
//})
}
_semaphoreSlim.Release();
} }
} }
} }
} }
@ -152,6 +155,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
Time = response.Trade.Time.ToDateTime().ToUniversalTime(), Time = response.Trade.Time.ToDateTime().ToUniversalTime(),
Value = response.Trade.Price, Value = response.Trade.Price,
IsHistoricalData = false, IsHistoricalData = false,
IsSellPrice = response.Trade.Direction == TradeDirection.Sell
}; };
await _eventBus.Broadcast(message); await _eventBus.Broadcast(message);

View File

@ -118,7 +118,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
foreach (var acc in accounts) 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) foreach (var asset in assets)
{ {
var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value,

View File

@ -17,6 +17,7 @@ using Tinkoff.InvestApi;
using Tinkoff.InvestApi.V1; using Tinkoff.InvestApi.V1;
using Asset = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.Asset; using Asset = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.Asset;
using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.AssetType; using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.AssetType;
using PositionType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.PositionType;
namespace KLHZ.Trader.Core.Exchange.Services 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; 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) internal async Task SyncPortfolio(ManagedAccount account)
{ {
try try
@ -231,6 +240,24 @@ namespace KLHZ.Trader.Core.Exchange.Services
#pragma warning restore CS0612 // Тип или член устарел #pragma warning restore CS0612 // Тип или член устарел
account.Assets.AddOrUpdate(asset.Figi, asset, (k, v) => asset); account.Assets.AddOrUpdate(asset.Figi, asset, (k, v) => asset);
oldAssets.Remove(asset.Figi); 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; account.Total = portfolio.TotalAmountPortfolio;

View File

@ -1,6 +1,5 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Threading.Channels; using System.Threading.Channels;
@ -42,18 +41,21 @@ namespace KLHZ.Trader.Core.Exchange.Services
try try
{ {
var dir = OrderDirection.Unspecified; var dir = OrderDirection.Unspecified;
var dealDirection = DealDirection.Unknown; var orderType = OrderType.Unspecified;
var sign = 1;
if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy) if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy)
{ {
dir = OrderDirection.Buy; dir = OrderDirection.Buy;
dealDirection = DealDirection.Buy; orderType = OrderType.Market;
} }
else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell) else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell)
{ {
sign = -1;
dir = OrderDirection.Sell; 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() var req = new PostOrderRequest()
@ -61,23 +63,24 @@ namespace KLHZ.Trader.Core.Exchange.Services
AccountId = tradeCommand.AccountId, AccountId = tradeCommand.AccountId,
InstrumentId = tradeCommand.Figi, InstrumentId = tradeCommand.Figi,
Direction = dir, Direction = dir,
OrderType = OrderType.Market, Price = tradeCommand.RecomendPrice ?? 0,
OrderType = orderType,
Quantity = tradeCommand.Count, Quantity = tradeCommand.Count,
ConfirmMarginTrade = tradeCommand.EnableMargin, ConfirmMarginTrade = tradeCommand.EnableMargin,
}; };
var res = await _investApiClient.Orders.PostOrderAsync(req); var res = await _investApiClient.Orders.PostOrderAsync(req);
var result = new DealResult //var result = new DealResult
{ //{
Count = sign * res.LotsExecuted, // Count = sign * res.LotsExecuted,
Price = res.ExecutedOrderPrice, // Price = res.ExecutedOrderPrice,
Success = true, // Success = true,
Direction = dealDirection, // Direction = dealDirection,
AccountId = tradeCommand.AccountId, // AccountId = tradeCommand.AccountId,
Figi = tradeCommand.Figi, // Figi = tradeCommand.Figi,
}; //};
await _tradeDataProvider.LogDeal(result); //await _tradeDataProvider.LogDeal(result);
} }
catch (Exception ex) catch (Exception ex)
{ {