чистка мусора + доработка вывода результатов
test / deploy_trader_prod (push) Successful in 11m24s Details

dev
vlad zverzhkhovskiy 2025-10-15 11:31:24 +03:00
parent b3b7807249
commit 6770d12640
10 changed files with 109 additions and 115 deletions

View File

@ -82,7 +82,6 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
return (resDt, resVs); return (resDt, resVs);
} }
public static (DateTime[] timestamps, decimal[] values) TrimValues(DateTime[] timestamps, decimal[] values, TimeSpan leftBound, TimeSpan rightBound) public static (DateTime[] timestamps, decimal[] values) TrimValues(DateTime[] timestamps, decimal[] values, TimeSpan leftBound, TimeSpan rightBound)
{ {
var resDt = new List<DateTime>(); var resDt = new List<DateTime>();

View File

@ -232,7 +232,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
} }
public static (TradingEvent events, decimal bigWindowAv, decimal smallWindowAv) CheckByWindowAverageMean2(ITradeDataItem[] data, int size, int smallWindow, int bigWindow, public static (TradingEvent events, decimal bigWindowAv, decimal smallWindowAv) CheckByWindowAverageMean2(ITradeDataItem[] data, int size, int smallWindow, int bigWindow,
decimal? uptrendStartingDetectionMeanfullStep = null, decimal? uptrendEndingDetectionMeanfullStep = null) decimal? uptrendStartingDetectionMeanfullStep = null, decimal? uptrendEndingDetectionMeanfullStep = null)
{ {
var res = TradingEvent.None; var res = TradingEvent.None;
var bigWindowAv = 0m; var bigWindowAv = 0m;

View File

@ -5,7 +5,6 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
{ {
public static class Statistics public static class Statistics
{ {
public static decimal MeanCount(this ITradeDataItem[] values) public static decimal MeanCount(this ITradeDataItem[] values)
{ {
return values.Sum(x => x.Count) / values.Length; return values.Sum(x => x.Count) / values.Length;
@ -197,25 +196,5 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
} }
return Array.Empty<ConvolutionResult>(); return Array.Empty<ConvolutionResult>();
} }
public static void MergeConvolutionResults(List<ConvolutionResult> results, List<ConvolutionResult> mergedResults)
{
if (results.Count == 0)
{
return;
}
var resultsOrdered = results.OrderBy(r => r.Sum).ToList();
var res = resultsOrdered[0];
var b1 = res.Shift - res.Leverage;
var b2 = res.Shift + res.Leverage;
var forMerge = results.Where(r => r.Shift >= b1 && r.Shift <= b2).ToList();
res.Sum = forMerge.Sum(r => r.Sum);
foreach (var m in forMerge)
{
results.Remove(m);
}
mergedResults.Add(res);
MergeConvolutionResults(results, mergedResults);
}
} }
} }

View File

@ -1,5 +1,6 @@
using KLHZ.Trader.Core.Contracts.Common.Enums; using KLHZ.Trader.Core.Contracts.Common.Enums;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using KLHZ.Trader.Core.Exchange.Models.Trading;
using System.Collections.Immutable; using System.Collections.Immutable;
namespace KLHZ.Trader.Core.Exchange.Interfaces namespace KLHZ.Trader.Core.Exchange.Interfaces
@ -14,8 +15,8 @@ namespace KLHZ.Trader.Core.Exchange.Interfaces
Task Init(string accountId, string? accountName = null); Task Init(string accountId, string? accountName = null);
Task LoadPortfolio(); Task LoadPortfolio();
ImmutableDictionary<string, Asset> Assets { get; } ImmutableDictionary<string, Asset> Assets { get; }
public Task OpenPosition(string figi, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1); public Task<TradeResult> OpenPosition(string figi, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1, decimal pointPrice = 1);
public Task ClosePosition(string figi); public Task<TradeResult> ClosePosition(string figi);
public Task ResetStops(string figi, decimal stopLossShift, decimal takeProfitShift); public Task ResetStops(string figi, decimal stopLossShift, decimal takeProfitShift);
} }
} }

View File

@ -5,6 +5,6 @@
public required string Figi { get; init; } public required string Figi { get; init; }
public decimal ShortLeverage { get; init; } public decimal ShortLeverage { get; init; }
public decimal LongLeverage { get; init; } public decimal LongLeverage { get; init; }
public decimal PriceToRubConvertationCoefficient { get; init; } = 1; public decimal PointPriceRub { get; init; } = 1;
} }
} }

View File

@ -0,0 +1,10 @@
namespace KLHZ.Trader.Core.Exchange.Models.Trading
{
public class TradeResult
{
public bool Success { get; init; }
public decimal ExecutedPrice { get; init; }
public decimal Comission { get; init; }
public long Count { get; init; }
}
}

View File

@ -1,12 +0,0 @@
namespace KLHZ.Trader.Core.Exchange.Models.Trading
{
public enum TradingMode
{
None = 0,
Stable = 1,
SlowDropping = -1,
SlowGrowing = 2,
Growing = 3,
Dropping = -2,
}
}

View File

@ -4,6 +4,7 @@ using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.Exchange.Extentions; using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Interfaces; using KLHZ.Trader.Core.Exchange.Interfaces;
using KLHZ.Trader.Core.Exchange.Models.Configs; using KLHZ.Trader.Core.Exchange.Models.Configs;
using KLHZ.Trader.Core.Exchange.Models.Trading;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -164,8 +165,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
Balance = portfolio.TotalAmountCurrencies; Balance = portfolio.TotalAmountCurrencies;
} }
public async Task OpenPosition(string figi, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1) public async Task<TradeResult> OpenPosition(string figi, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1, decimal pointPrice = 1)
{ {
TradeResult? result = null;
try try
{ {
await _semaphore.WaitAsync2(_defaultLockTimeSpan); await _semaphore.WaitAsync2(_defaultLockTimeSpan);
@ -186,58 +188,54 @@ namespace KLHZ.Trader.Core.Exchange.Services
var res = await _investApiClient.Orders.PostOrderAsync(req); var res = await _investApiClient.Orders.PostOrderAsync(req);
_usedOrderIds.TryAdd(res.OrderId, DateTime.UtcNow); _usedOrderIds.TryAdd(res.OrderId, DateTime.UtcNow);
var executedPrice = res.ExecutedOrderPrice / 10; var executedPrice = res.ExecutedOrderPrice / pointPrice;
await Task.Delay(1000); if (stopLossShift != 0)
if (stopLossShift == 0)
{ {
stopLossShift = 0.002m * executedPrice; var pricesl = positionType == PositionType.Long ? executedPrice - stopLossShift : executedPrice + stopLossShift;
var slReq = new PostStopOrderRequest()
{
AccountId = AccountId,
ConfirmMarginTrade = false,
InstrumentId = figi,
Direction = stopOrdersDirection,
PriceType = PriceType.Point,
Quantity = count,
StopOrderType = StopOrderType.StopLoss,
StopPrice = pricesl,
ExchangeOrderType = ExchangeOrderType.Market,
ExpirationType = StopOrderExpirationType.GoodTillCancel,
};
var slOrderRes = await _investApiClient.StopOrders.PostStopOrderAsync(slReq);
} }
if (takeProfitShift == 0)
if (takeProfitShift != 0)
{ {
takeProfitShift = 0.01m * executedPrice; var pricetp = positionType == PositionType.Long ? executedPrice + takeProfitShift : executedPrice - takeProfitShift;
var tpReq = new PostStopOrderRequest()
{
AccountId = AccountId,
ConfirmMarginTrade = false,
InstrumentId = figi,
Direction = stopOrdersDirection,
PriceType = PriceType.Point,
Quantity = count,
StopOrderType = StopOrderType.TakeProfit,
StopPrice = pricetp,
ExchangeOrderType = ExchangeOrderType.Market,
ExpirationType = StopOrderExpirationType.GoodTillCancel,
};
var tpOrderRes = await _investApiClient.StopOrders.PostStopOrderAsync(tpReq);
} }
takeProfitShift = takeProfitShift * 2;
takeProfitShift = System.Math.Round(takeProfitShift);
takeProfitShift = takeProfitShift / 2;
stopLossShift = stopLossShift * 2;
stopLossShift = System.Math.Round(stopLossShift);
stopLossShift = stopLossShift / 2;
var pricesl = positionType == PositionType.Long ? executedPrice - stopLossShift : executedPrice + stopLossShift;
var pricetp = positionType == PositionType.Long ? executedPrice + takeProfitShift : executedPrice - takeProfitShift;
var slReq = new PostStopOrderRequest()
{
AccountId = AccountId,
ConfirmMarginTrade = false,
InstrumentId = figi,
Direction = stopOrdersDirection,
PriceType = PriceType.Point,
Quantity = count,
StopOrderType = StopOrderType.StopLoss,
StopPrice = pricesl,
ExchangeOrderType = ExchangeOrderType.Market,
ExpirationType = StopOrderExpirationType.GoodTillCancel,
};
var slOrderRes = await _investApiClient.StopOrders.PostStopOrderAsync(slReq);
var tpReq = new PostStopOrderRequest()
{
AccountId = AccountId,
ConfirmMarginTrade = false,
InstrumentId = figi,
Direction = stopOrdersDirection,
PriceType = PriceType.Point,
Quantity = count,
StopOrderType = StopOrderType.TakeProfit,
StopPrice = pricetp,
ExchangeOrderType = ExchangeOrderType.Market,
ExpirationType = StopOrderExpirationType.GoodTillCancel,
};
var tpOrderRes = await _investApiClient.StopOrders.PostStopOrderAsync(tpReq);
await LoadPortfolioNolock(); await LoadPortfolioNolock();
result = new TradeResult()
{
Success = true,
Comission = res.ExecutedCommission,
Count = res.LotsExecuted,
ExecutedPrice = res.ExecutedOrderPrice,
};
} }
} }
catch (TaskCanceledException) { } catch (TaskCanceledException) { }
@ -247,6 +245,8 @@ namespace KLHZ.Trader.Core.Exchange.Services
} }
_semaphore.Release(); _semaphore.Release();
result ??= new TradeResult();
return result;
} }
public async Task ResetStops(string figi, decimal stopLossShift, decimal takeProfitShift) public async Task ResetStops(string figi, decimal stopLossShift, decimal takeProfitShift)
@ -317,8 +317,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
_semaphore.Release(); _semaphore.Release();
} }
public async Task ClosePosition(string figi) public async Task<TradeResult> ClosePosition(string figi)
{ {
TradeResult? result = null;
try try
{ {
await _semaphore.WaitAsync2(_defaultLockTimeSpan); await _semaphore.WaitAsync2(_defaultLockTimeSpan);
@ -355,6 +356,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
} }
await LoadPortfolioNolock(); await LoadPortfolioNolock();
result = new TradeResult()
{
Success = true,
Comission = res.ExecutedCommission,
Count = res.LotsExecuted,
ExecutedPrice = res.ExecutedOrderPrice,
};
} }
} }
catch (TaskCanceledException) { } catch (TaskCanceledException) { }
@ -364,6 +372,8 @@ namespace KLHZ.Trader.Core.Exchange.Services
} }
_semaphore.Release(); _semaphore.Release();
result ??= new TradeResult();
return result;
} }
private async Task CyclingOperations() private async Task CyclingOperations()

View File

@ -32,12 +32,10 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly ExchangeConfig _exchangeConfig; private readonly ExchangeConfig _exchangeConfig;
private readonly ILogger<Trader> _logger; private readonly ILogger<Trader> _logger;
private readonly ConcurrentDictionary<string, TradingMode> TradingModes = new();
private readonly ConcurrentDictionary<string, DeferredDeclision> DeferredDeclisions = new(); private readonly ConcurrentDictionary<string, DeferredDeclision> DeferredDeclisions = new();
private readonly ConcurrentDictionary<string, SupportLevel[]> SupportLevels = new(); private readonly ConcurrentDictionary<string, SupportLevel[]> SupportLevels = new();
private readonly ConcurrentDictionary<string, decimal> _pirsonValues = new(); private readonly ConcurrentDictionary<string, decimal> _pirsonValues = new();
private readonly ConcurrentDictionary<string, decimal> _dpirsonValues = new();
private readonly ConcurrentDictionary<string, DateTime> _supportLevelsCalculationTimes = new(); private readonly ConcurrentDictionary<string, DateTime> _supportLevelsCalculationTimes = new();
private readonly ConcurrentDictionary<string, DateTime> _usedSupportLevels = new(); private readonly ConcurrentDictionary<string, DateTime> _usedSupportLevels = new();
private readonly ConcurrentDictionary<string, DateTime> _usedSupportLevelsForClosing = new(); private readonly ConcurrentDictionary<string, DateTime> _usedSupportLevelsForClosing = new();
@ -59,10 +57,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
_logger = logger; _logger = logger;
_dataBus = dataBus; _dataBus = dataBus;
_exchangeConfig = options.Value; _exchangeConfig = options.Value;
foreach (var f in _exchangeConfig.TradingInstrumentsFigis)
{
TradingModes[f] = TradingMode.None;
}
} }
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
@ -481,32 +475,27 @@ namespace KLHZ.Trader.Core.Exchange.Services
{ {
Asset? assetForClose = null; Asset? assetForClose = null;
string? mess = null; string? mess = null;
var profit = 0m;
if (withProfitOnly) if (withProfitOnly)
{ {
var profit = 0m; if (_tradeDataProvider.Orderbooks.TryGetValue(message.Figi, out var orderbook))
if (assetType == AssetType.Futures)
{ {
if (_tradeDataProvider.Orderbooks.TryGetValue(message.Figi, out var orderbook)) if (asset.Count < 0 && orderbook.Asks.Length > 0)
{ {
if (asset.Count < 0 && orderbook.Asks.Length > 0) price = orderbook.Asks[0].Price;
{ }
price = orderbook.Asks[0].Price; else if (orderbook.Bids.Length > 0)
} {
else if (orderbook.Bids.Length > 0) price = orderbook.Bids[0].Price;
{
price = orderbook.Bids[0].Price;
}
} }
profit = TradingCalculator.CaclProfit(asset.BoughtPrice, price,
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
} }
profit = TradingCalculator.CaclProfit(asset.BoughtPrice, price,
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
if (profit > 0) if (profit > 0)
{ {
profit = System.Math.Round(profit, 2); profit = System.Math.Round(profit, 2);
assetForClose = asset; assetForClose = asset;
mess = $"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{price}, профит {profit}";
if (loggedDeclisions == 0) if (loggedDeclisions == 0)
{ {
loggedDeclisions++; loggedDeclisions++;
@ -516,13 +505,23 @@ namespace KLHZ.Trader.Core.Exchange.Services
} }
else else
{ {
mess = $"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{price}";
assetForClose = asset; assetForClose = asset;
} }
if (assetForClose != null && mess != null) if (assetForClose != null)
{ {
await _portfolioWrapper.Accounts[asset.AccountId].ClosePosition(message.Figi); var settings = _exchangeConfig.InstrumentsSettings.FirstOrDefault(l => l.Figi == message.Figi);
var closingResult = await _portfolioWrapper.Accounts[asset.AccountId].ClosePosition(message.Figi);
if (closingResult.Success)
{
var profitText = profit == 0 ? string.Empty : ", профит {profit}";
mess = $"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{closingResult.ExecutedPrice / (settings?.PointPriceRub ?? 1)}, комиссия {closingResult.Comission}" + profitText;
}
else
{
mess = $"Закрытие позиции прошло с ошибками.";
}
await _dataBus.Broadcast(new MessageForAdmin() { Text = mess }); await _dataBus.Broadcast(new MessageForAdmin() { Text = mess });
} }
} }
@ -537,12 +536,20 @@ namespace KLHZ.Trader.Core.Exchange.Services
{ {
if (TraderUtils.IsOperationAllowed(acc, message.Price, count, _exchangeConfig.AccountCashPartFutures, _exchangeConfig.AccountCashPart)) if (TraderUtils.IsOperationAllowed(acc, message.Price, count, _exchangeConfig.AccountCashPartFutures, _exchangeConfig.AccountCashPart))
{ {
await acc.OpenPosition(message.Figi, positionType, stopLossShift, takeProfitShift, count); var settings = _exchangeConfig.InstrumentsSettings.FirstOrDefault(l => l.Figi == message.Figi);
takeProfitShift = takeProfitShift * 2;
takeProfitShift = System.Math.Round(takeProfitShift);
takeProfitShift = takeProfitShift / 2;
stopLossShift = stopLossShift * 2;
stopLossShift = System.Math.Round(stopLossShift);
stopLossShift = stopLossShift / 2;
var openingResult = await acc.OpenPosition(message.Figi, positionType, stopLossShift, takeProfitShift, count, settings?.PointPriceRub ?? 1);
await _dataBus.Broadcast(new MessageForAdmin() await _dataBus.Broadcast(new MessageForAdmin()
{ {
Text = $"Открываю позицию {message.Figi} ({(positionType == PositionType.Long ? "лонг" : "шорт")}) " + Text = $"Открываю позицию {message.Figi} ({(positionType == PositionType.Long ? "лонг" : "шорт")}) " +
$"на счёте {acc.AccountName}. Количество {(positionType == PositionType.Long ? "" : "-")}{count}, " + $"на счёте {acc.AccountName}. Количество {(positionType == PositionType.Long ? "" : "-")}{openingResult.Count}, " +
$"цена ~{System.Math.Round(message.Price, 2)}. Стоп лосс: {(positionType == PositionType.Long ? "-" : "+")}{stopLossShift}. " + $"цена ~{System.Math.Round(openingResult.ExecutedPrice / (settings?.PointPriceRub ?? 1), 2)}. Комиссия:{System.Math.Round(openingResult.Comission, 2)}. Стоп лосс: {(positionType == PositionType.Long ? "-" : "+")}{stopLossShift}. " +
$"Тейк профит: {(positionType == PositionType.Long ? "+" : "-")}{takeProfitShift}" $"Тейк профит: {(positionType == PositionType.Long ? "+" : "-")}{takeProfitShift}"
}); });
} }

View File

@ -22,7 +22,7 @@
"Figi": "FUTIMOEXF000", "Figi": "FUTIMOEXF000",
"LongLeverage": 10.3, "LongLeverage": 10.3,
"ShortLeverage": 7.9, "ShortLeverage": 7.9,
"PriceToRubConvertationCoefficient" : 1 "PointPriceRub": 10
} }
] ]
}, },