обновление логики + обновление механизма логирования сделок
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,
MarketSell = 101,
LimitBuy = 200,
}
}

View File

@ -7,5 +7,6 @@
public string Figi { get; set; }
public string Ticker { 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 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; }
}
}

View File

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

View File

@ -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);

View File

@ -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);
}
}
}

View File

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

View File

@ -23,5 +23,7 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
[NotMapped]
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")]
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; }
}
}

View File

@ -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<ExchangeConfig> options, IDbContextFactory<TraderDbContext> dbContextFactory,
ILogger<ExchangeDataReader> 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);

View File

@ -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,

View File

@ -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;

View File

@ -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)
{