Добавил режимы торговли
test / deploy_trader_prod (push) Successful in 6m22s
Details
test / deploy_trader_prod (push) Successful in 6m22s
Details
parent
0f5284f472
commit
6643549a93
|
@ -7,5 +7,7 @@
|
|||
MarketBuy = 1,
|
||||
MarketSell = 101,
|
||||
LimitBuy = 200,
|
||||
LimitSell = 300,
|
||||
CancelOrder = 400,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
||||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
||||
{
|
||||
public interface ILockableObject
|
||||
{
|
||||
|
|
|
@ -10,7 +10,8 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
|||
public decimal? RecomendPrice { get; }
|
||||
public long Count { get; }
|
||||
public string AccountId { get; }
|
||||
public string? OrderId { get; }
|
||||
public bool EnableMargin { get; }
|
||||
public ILockableObject? ExchangeObject { get; }
|
||||
public ILockableObject? ExchangeObject { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,6 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
|
|||
public required string AccountId { get; init; }
|
||||
public bool EnableMargin { get; init; } = true;
|
||||
public ILockableObject? ExchangeObject { get; init; }
|
||||
public string? OrderId { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ namespace KLHZ.Trader.Core.Tests
|
|||
[Test]
|
||||
public void CalcProfitTest()
|
||||
{
|
||||
var profit = TradingCalculator.CaclProfit(2990, 2985m, 0.0025m, 10.3m, false);
|
||||
var profit = TradingCalculator.CaclProfit(2990, 2987m, 0.0025m, 7.9m, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
|
|||
}
|
||||
|
||||
internal readonly ConcurrentDictionary<string, Asset> Assets = new();
|
||||
internal readonly ConcurrentDictionary<string, Order> Orders = new();
|
||||
|
||||
public ManagedAccount(string accountId)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
|
||||
{
|
||||
public class Order
|
||||
{
|
||||
public required string AccountId { get; init; }
|
||||
public required string Figi { get; init; }
|
||||
public required string Ticker { get; init; }
|
||||
public required string OrderId { get; init; }
|
||||
public decimal Price { get; init; }
|
||||
public long Count { get; init; }
|
||||
public DateTime ExpirationTime { get; init; }
|
||||
public DateTime OpenDate { get; init; }
|
||||
public DealDirection Direction { get; init; }
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace KLHZ.Trader.Core.Exchange.Models.Trading
|
||||
{
|
||||
internal class DeferredTrade
|
||||
{
|
||||
public required string Figi { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace KLHZ.Trader.Core.Exchange.Models.Trading
|
||||
{
|
||||
public enum TradingMode
|
||||
{
|
||||
None = 0,
|
||||
Stable = 1,
|
||||
SlowDropping = -1,
|
||||
Growing = 2,
|
||||
Dropping = -2,
|
||||
}
|
||||
}
|
|
@ -30,6 +30,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
private readonly IDataBus _dataBus;
|
||||
private readonly TraderDataProvider _tradeDataProvider;
|
||||
private readonly ILogger<Trader> _logger;
|
||||
|
||||
private readonly ConcurrentDictionary<string, TradingMode> TradingModes = new();
|
||||
|
||||
private readonly ConcurrentDictionary<string, DateTime> LongOpeningStops = new();
|
||||
private readonly ConcurrentDictionary<string, DateTime> LongClosingStops = new();
|
||||
private readonly ConcurrentDictionary<string, DateTime> ShortClosingStops = new();
|
||||
|
@ -40,7 +43,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
private readonly decimal _accountCashPart;
|
||||
private readonly decimal _accountCashPartFutures;
|
||||
private readonly string[] _tradingInstrumentsFigis = [];
|
||||
|
||||
private readonly bool _isDebug = false;
|
||||
private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
|
||||
private readonly Channel<IOrderbook> _ordersbookChannel = Channel.CreateUnbounded<IOrderbook>();
|
||||
|
||||
|
@ -59,6 +62,11 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
_accountCashPart = options.Value.AccountCashPart;
|
||||
_accountCashPartFutures = options.Value.AccountCashPartFutures;
|
||||
_tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis;
|
||||
_isDebug = !options.Value.ExchangeDataRecievingEnabled;
|
||||
foreach (var f in _tradingInstrumentsFigis)
|
||||
{
|
||||
TradingModes[f] = TradingMode.None;
|
||||
}
|
||||
|
||||
foreach (var lev in options.Value.InstrumentsSettings)
|
||||
{
|
||||
|
@ -72,6 +80,11 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
_dataBus.AddChannel(nameof(Trader), _pricesChannel);
|
||||
_dataBus.AddChannel(nameof(Trader), _ordersbookChannel);
|
||||
_ = ProcessPrices();
|
||||
_ = ProcessOrders();
|
||||
if (!_isDebug)
|
||||
{
|
||||
_ = TradingModeUpdatingWorker();
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(INewPrice message)
|
||||
|
@ -122,9 +135,12 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
private async Task ProcessPrices()
|
||||
{
|
||||
var pricesCache = new Dictionary<string, List<INewPrice>>();
|
||||
var timesCache = new Dictionary<string, DateTime>();
|
||||
while (await _pricesChannel.Reader.WaitToReadAsync())
|
||||
{
|
||||
var message = await _pricesChannel.Reader.ReadAsync();
|
||||
|
||||
#region Ускорение обработки исторических данных при отладке
|
||||
if (message.IsHistoricalData)
|
||||
{
|
||||
await _tradeDataProvider.AddData(message, TimeSpan.FromHours(6));
|
||||
|
@ -154,7 +170,32 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
};
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (timesCache.TryGetValue(message.Figi, out var dt))
|
||||
{
|
||||
if ((message.Time - dt).TotalSeconds > 120)
|
||||
{
|
||||
timesCache[message.Figi] = message.Time;
|
||||
|
||||
TradingModes[message.Figi] = await CalcTradingMode(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
timesCache[message.Figi] = message.Time;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
await LogPrice(message, "trading_mode", (int)TradingModes[message.Figi]);
|
||||
//continue;
|
||||
#endregion
|
||||
if (message.Figi == "BBG004730N88")
|
||||
{
|
||||
if (message.Direction == 1)
|
||||
|
@ -191,15 +232,27 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
|
||||
try
|
||||
{
|
||||
if (message.Figi == "FUTIMOEXF000" && message.Direction == 1)
|
||||
ProcessStops(message, currentTime);
|
||||
var windowMaxSize = 2000;
|
||||
await SellAssetsIfNeed(message);
|
||||
var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize);
|
||||
var state = ExchangeScheduler.GetCurrentState(message.Time);
|
||||
await ProcessClearing(data, state, message);
|
||||
|
||||
if (TradingModes[message.Figi] == TradingMode.Stable)
|
||||
{
|
||||
await ProcessNewPriceIMOEXF_Stable(data, state, message, windowMaxSize);
|
||||
}
|
||||
else if (TradingModes[message.Figi] == TradingMode.SlowDropping)
|
||||
{
|
||||
await ProcessNewPriceIMOEXF_Dropping(data,state,message,windowMaxSize,3);
|
||||
}
|
||||
else if (TradingModes[message.Figi] == TradingMode.Dropping)
|
||||
{
|
||||
await ProcessNewPriceIMOEXF_Dropping(data, state, message, windowMaxSize, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessStops(message, currentTime);
|
||||
var windowMaxSize = 2000;
|
||||
await SellAssetsIfNeed(message);
|
||||
var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize);
|
||||
var state = ExchangeScheduler.GetCurrentState(message.Time);
|
||||
await ProcessClearing(data, state, message);
|
||||
|
||||
await ProcessNewPriceIMOEXF2(data, state, message, windowMaxSize);
|
||||
}
|
||||
}
|
||||
|
@ -211,6 +264,43 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ProcessOrders()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await ProcessOrdersAction();
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessOrdersAction(bool cancellAll = false, string? figi = null)
|
||||
{
|
||||
var accounts = _tradeDataProvider.Accounts.Values.ToArray();
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
foreach (var order in account.Orders)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(figi))
|
||||
{
|
||||
if (order.Value.Figi != figi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (cancellAll || order.Value.ExpirationTime < DateTime.UtcNow)
|
||||
{
|
||||
await _dataBus.Broadcast(new TradeCommand()
|
||||
{
|
||||
AccountId = account.AccountId,
|
||||
Figi = "",
|
||||
OrderId = order.Key,
|
||||
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.CancelOrder,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SellAssetsIfNeed(INewPrice message)
|
||||
{
|
||||
if (!BotModeSwitcher.CanSell())
|
||||
|
@ -266,29 +356,24 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
return resultMoveAvFull.events;
|
||||
}
|
||||
|
||||
private async Task<TradingEvent> CheckByWindowAverageMeanNolog((DateTime[] timestamps, decimal[] prices) data,
|
||||
private Task<TradingEvent> CheckByWindowAverageMeanNolog((DateTime[] timestamps, decimal[] prices) data,
|
||||
INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m)
|
||||
{
|
||||
var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices,
|
||||
windowMaxSize, 30, 180, TimeSpan.FromSeconds(20), uptrendStartingDetectionMeanfullStep, uptrendEndingDetectionMeanfullStep);
|
||||
return resultMoveAvFull.events;
|
||||
return Task.FromResult(resultMoveAvFull.events);
|
||||
}
|
||||
|
||||
private async Task<TradingEvent> CheckByWindowAverageMeanForShotrs((DateTime[] timestamps, decimal[] prices) data,
|
||||
private Task<TradingEvent> CheckByWindowAverageMeanForShotrs((DateTime[] timestamps, decimal[] prices) data,
|
||||
INewPrice message, int windowMaxSize)
|
||||
{
|
||||
var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices,
|
||||
windowMaxSize, 30, 240, TimeSpan.FromSeconds(20), -1m, 1m);
|
||||
if (resultMoveAvFull.bigWindowAv != 0)
|
||||
{
|
||||
//await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv);
|
||||
//await LogPrice(message, Constants.SmallWindowCrossingAverageProcessor, resultMoveAvFull.smallWindowAv);
|
||||
}
|
||||
return resultMoveAvFull.events;
|
||||
return Task.FromResult(resultMoveAvFull.events);
|
||||
}
|
||||
|
||||
private Task<TradingEvent> CheckByLocalTrends((DateTime[] timestamps, decimal[] prices) data,
|
||||
INewPrice message, int windowMaxSize)
|
||||
INewPrice message, int windowMaxSize)
|
||||
{
|
||||
var res = TradingEvent.None;
|
||||
if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(20), 1, out var resLocalTrends))
|
||||
|
@ -299,14 +384,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
res |= TradingEvent.DowntrendEnd;
|
||||
}
|
||||
}
|
||||
//if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(30), 2, out var resLocalTrends2))
|
||||
//{
|
||||
// res |= (resLocalTrends & TradingEvent.DowntrendEnd);
|
||||
//}
|
||||
//if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(20), 2.5, out var resLocalTrends3))
|
||||
//{
|
||||
// res |= (resLocalTrends & TradingEvent.DowntrendStart);
|
||||
//}
|
||||
return Task.FromResult(res);
|
||||
}
|
||||
|
||||
|
@ -575,6 +652,221 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ProcessNewPriceIMOEXF_Stable(
|
||||
(DateTime[] timestamps, decimal[] prices) data,
|
||||
ExchangeState state,
|
||||
INewPrice message, int windowMaxSize)
|
||||
{
|
||||
if (data.timestamps.Length <= 4 || state!=ExchangeState.Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mavTask = CheckByWindowAverageMean(data, message, windowMaxSize, -1, 1m);
|
||||
var ltTask = CheckByLocalTrends(data, message, windowMaxSize);
|
||||
var positionTask = CheckPosition(message);
|
||||
|
||||
await Task.WhenAll(mavTask, ltTask, positionTask);
|
||||
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
|
||||
var res = mavTask.Result | ltTask.Result;
|
||||
|
||||
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart && (positionTask.Result == ValueAmplitudePosition.None || positionTask.Result == ValueAmplitudePosition.LowerThenMediana))
|
||||
{
|
||||
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
|
||||
{
|
||||
var accounts = _tradeDataProvider.Accounts
|
||||
.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
|
||||
.ToArray();
|
||||
var loggedDeclisions = 0;
|
||||
foreach (var acc in accounts)
|
||||
{
|
||||
if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart))
|
||||
{
|
||||
if (RandomNumberGenerator.GetInt32(100) > 50 && await acc.Value.Lock(TimeSpan.FromSeconds(12)))
|
||||
{
|
||||
var command = new TradeCommand()
|
||||
{
|
||||
AccountId = acc.Value.AccountId,
|
||||
Figi = message.Figi,
|
||||
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitBuy,
|
||||
Count = 1,
|
||||
RecomendPrice = message.Value - 0.5m,
|
||||
ExchangeObject = acc.Value,
|
||||
};
|
||||
|
||||
await _dataBus.Broadcast(command);
|
||||
_logger.LogWarning("Выставлена заявка на покупку актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
|
||||
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
|
||||
if (loggedDeclisions == 0)
|
||||
{
|
||||
await LogDeclision(DeclisionTradeAction.OpenLongReal, message);
|
||||
LongOpeningStops[message.Figi] = message.Time.AddMinutes(1);
|
||||
loggedDeclisions++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await LogDeclision(DeclisionTradeAction.OpenLong, message);
|
||||
}
|
||||
|
||||
foreach (var acc in _tradeDataProvider.Accounts)
|
||||
{
|
||||
if (acc.Value.Assets.TryGetValue(message.Figi, out var asset))
|
||||
{
|
||||
var order = acc.Value.Orders.Values.FirstOrDefault(o => o.Figi == message.Figi && o.Direction == DealDirection.Sell);
|
||||
if (order == null && asset.Count>0)
|
||||
{
|
||||
var command = new TradeCommand()
|
||||
{
|
||||
AccountId = asset.AccountId,
|
||||
Figi = message.Figi,
|
||||
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitSell,
|
||||
Count = (long)asset.Count,
|
||||
RecomendPrice = asset.BoughtPrice + 3,
|
||||
EnableMargin = false,
|
||||
};
|
||||
await _dataBus.Broadcast(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessNewPriceIMOEXF_Dropping(
|
||||
(DateTime[] timestamps, decimal[] prices) data,
|
||||
ExchangeState state,
|
||||
INewPrice message, int windowMaxSize, decimal step)
|
||||
{
|
||||
if (data.timestamps.Length <= 4 && state !=ExchangeState.Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mavTask = CheckByWindowAverageMean(data, message, windowMaxSize, -1, 1m);
|
||||
var ltTask = CheckByLocalTrends(data, message, windowMaxSize);
|
||||
var positionTask = CheckPosition(message);
|
||||
|
||||
await Task.WhenAll(mavTask, ltTask, positionTask);
|
||||
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
|
||||
var res = mavTask.Result | ltTask.Result;
|
||||
|
||||
if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd && (positionTask.Result != ValueAmplitudePosition.LowerThenMediana))
|
||||
{
|
||||
if (!message.IsHistoricalData && BotModeSwitcher.CanSell())
|
||||
{
|
||||
var accounts = _tradeDataProvider.Accounts
|
||||
.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
|
||||
.ToArray();
|
||||
var loggedDeclisions = 0;
|
||||
foreach (var acc in accounts)
|
||||
{
|
||||
if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart))
|
||||
{
|
||||
if (RandomNumberGenerator.GetInt32(100) > 50 && await acc.Value.Lock(TimeSpan.FromSeconds(12)))
|
||||
{
|
||||
var command = new TradeCommand()
|
||||
{
|
||||
AccountId = acc.Value.AccountId,
|
||||
Figi = message.Figi,
|
||||
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell,
|
||||
Count = 1,
|
||||
RecomendPrice = message.Value,
|
||||
ExchangeObject = acc.Value,
|
||||
};
|
||||
|
||||
await _dataBus.Broadcast(command);
|
||||
_logger.LogWarning("Выставлена заявка на продажу в шорт актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
|
||||
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
|
||||
if (loggedDeclisions == 0)
|
||||
{
|
||||
await LogDeclision(DeclisionTradeAction.OpenLongReal, message);
|
||||
LongOpeningStops[message.Figi] = message.Time.AddMinutes(1);
|
||||
loggedDeclisions++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await LogDeclision(DeclisionTradeAction.OpenShort, message);
|
||||
}
|
||||
|
||||
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart)
|
||||
{
|
||||
if (!ShortClosingStops.ContainsKey(message.Figi))
|
||||
{
|
||||
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
|
||||
{
|
||||
var loggedDeclisions = 0;
|
||||
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)
|
||||
{
|
||||
if (await asset.Lock(TimeSpan.FromSeconds(60)))
|
||||
{
|
||||
var profit = 0m;
|
||||
|
||||
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)
|
||||
{
|
||||
var command = new TradeCommand()
|
||||
{
|
||||
AccountId = asset.AccountId,
|
||||
Figi = message.Figi,
|
||||
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy,
|
||||
Count = System.Math.Abs((long)asset.Count),
|
||||
RecomendPrice = null,
|
||||
EnableMargin = false,
|
||||
};
|
||||
await _dataBus.Broadcast(command);
|
||||
_logger.LogWarning("Продажа актива {figi}! id команды {commandId}. Направление сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
|
||||
message.Figi, command.CommandId, command.CommandType, command.Count, command.EnableMargin);
|
||||
if (loggedDeclisions == 0)
|
||||
{
|
||||
loggedDeclisions++;
|
||||
await LogDeclision(DeclisionTradeAction.CloseShortReal, message, profit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.IsHistoricalData)
|
||||
{
|
||||
ShortClosingStops[message.Figi] = message.Time.AddSeconds(30);
|
||||
}
|
||||
await LogDeclision(DeclisionTradeAction.CloseShort, message);
|
||||
}
|
||||
}
|
||||
foreach (var acc in _tradeDataProvider.Accounts)
|
||||
{
|
||||
if (acc.Value.Assets.TryGetValue(message.Figi, out var asset))
|
||||
{
|
||||
var order = acc.Value.Orders.Values.FirstOrDefault(o => o.Figi == message.Figi && o.Direction == DealDirection.Buy);
|
||||
if (order == null && asset.Count<0)
|
||||
{
|
||||
var command = new TradeCommand()
|
||||
{
|
||||
AccountId = asset.AccountId,
|
||||
Figi = message.Figi,
|
||||
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitBuy,
|
||||
Count = System.Math.Abs((long)asset.Count),
|
||||
RecomendPrice = asset.BoughtPrice - step,
|
||||
EnableMargin = false,
|
||||
};
|
||||
await _dataBus.Broadcast(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessClearing((DateTime[] timestamps, decimal[] prices) data, ExchangeState state, INewPrice message)
|
||||
{
|
||||
if (state == ExchangeState.ClearingTime
|
||||
|
@ -668,6 +960,63 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
return res;
|
||||
}
|
||||
|
||||
private async Task<TradingMode> CalcTradingMode(string figi)
|
||||
{
|
||||
var res = TradingMode.None;
|
||||
var largeData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(90));
|
||||
var smallData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(15));
|
||||
|
||||
if (largeData.isFullIntervalExists && smallData.isFullIntervalExists)
|
||||
{
|
||||
if (LocalTrends.TryCalcTrendDiff(largeData.timestamps, largeData.prices, out var largeDataRes)
|
||||
&& LocalTrends.TryCalcTrendDiff(smallData.timestamps, smallData.prices, out var smallDataRes))
|
||||
{
|
||||
if (largeDataRes>0 && largeDataRes <= 4 && System.Math.Abs(smallDataRes)<3)
|
||||
{
|
||||
res = TradingMode.Stable;
|
||||
}
|
||||
if (largeDataRes < 0 && largeDataRes >= -5 && smallDataRes < 1)
|
||||
{
|
||||
res = TradingMode.SlowDropping;
|
||||
}
|
||||
if (largeDataRes>5 && smallDataRes > 0)
|
||||
{
|
||||
res = TradingMode.Growing;
|
||||
}
|
||||
if (largeDataRes < -5 && smallDataRes < 0)
|
||||
{
|
||||
res = TradingMode.Dropping;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private async Task<TradingMode> CalcTradingMode(INewPrice message)
|
||||
{
|
||||
var res = await CalcTradingMode(message.Figi);
|
||||
//await LogPrice(message, "trading_mode", (int)res);
|
||||
return res;
|
||||
}
|
||||
|
||||
private async Task TradingModeUpdatingWorker()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var figi in _tradingInstrumentsFigis)
|
||||
{
|
||||
TradingModes[figi] = await CalcTradingMode(figi);
|
||||
}
|
||||
await Task.Delay(120000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Ошибка при вычислении режима торговли.");
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static bool IsBuyAllowed(ManagedAccount account, decimal boutPrice, decimal count,
|
||||
decimal accountCashPartFutures, decimal accountCashPart)
|
||||
{
|
||||
|
|
|
@ -21,6 +21,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 Order = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.Order;
|
||||
using PositionType = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.PositionType;
|
||||
|
||||
namespace KLHZ.Trader.Core.Exchange.Services
|
||||
|
@ -167,9 +168,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
|
||||
public async Task Init()
|
||||
{
|
||||
await _initSemaphore.WaitAsync(TimeSpan.FromSeconds(3));
|
||||
try
|
||||
{
|
||||
await _initSemaphore.WaitAsync(TimeSpan.FromSeconds(3));
|
||||
var shares = await _investApiClient.Instruments.SharesAsync();
|
||||
foreach (var share in shares.Instruments)
|
||||
{
|
||||
|
@ -325,11 +326,31 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
await context.Trades
|
||||
.Where(t => ids.Contains(t.Id))
|
||||
.ExecuteUpdateAsync(t => t.SetProperty(tr => tr.ArchiveStatus, 1));
|
||||
|
||||
var orders = await _investApiClient.Orders.GetOrdersAsync(new GetOrdersRequest() { AccountId = account.AccountId });
|
||||
var actualOrders = orders.Orders.Select(o => new Order()
|
||||
{
|
||||
AccountId = account.AccountId,
|
||||
Figi = o.Figi,
|
||||
OrderId = o.OrderId,
|
||||
Ticker = GetTickerByFigi(o.Figi),
|
||||
Count = o.LotsRequested,
|
||||
ExpirationTime = DateTime.UtcNow.AddMinutes(10),
|
||||
OpenDate = DateTime.UtcNow,
|
||||
Price = o.AveragePositionPrice,
|
||||
Direction = (DealDirection)(int)o.Direction
|
||||
}).ToArray();
|
||||
|
||||
foreach (var order in actualOrders)
|
||||
{
|
||||
account.Orders[order.OrderId] = order;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Ошибка при синхранизации портфеля счёта {accountId}", account.AccountId);
|
||||
}
|
||||
|
||||
_initSemaphore.Release();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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;
|
||||
|
@ -40,52 +41,76 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
{
|
||||
try
|
||||
{
|
||||
var dir = OrderDirection.Unspecified;
|
||||
var orderType = OrderType.Unspecified;
|
||||
if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy)
|
||||
if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.CancelOrder && !string.IsNullOrEmpty(tradeCommand.OrderId))
|
||||
{
|
||||
dir = OrderDirection.Buy;
|
||||
orderType = OrderType.Market;
|
||||
var res = await _investApiClient.Orders.CancelOrderAsync(new CancelOrderRequest() { AccountId = tradeCommand.AccountId, OrderId = tradeCommand.OrderId });
|
||||
}
|
||||
else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell)
|
||||
else
|
||||
{
|
||||
dir = OrderDirection.Sell;
|
||||
orderType = OrderType.Market;
|
||||
var dir = OrderDirection.Unspecified;
|
||||
var orderType = OrderType.Unspecified;
|
||||
if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy)
|
||||
{
|
||||
dir = OrderDirection.Buy;
|
||||
orderType = OrderType.Market;
|
||||
}
|
||||
else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell)
|
||||
{
|
||||
dir = OrderDirection.Sell;
|
||||
orderType = OrderType.Market;
|
||||
}
|
||||
else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitBuy && tradeCommand.RecomendPrice.HasValue)
|
||||
{
|
||||
dir = OrderDirection.Buy;
|
||||
orderType = OrderType.Limit;
|
||||
}
|
||||
else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitSell && tradeCommand.RecomendPrice.HasValue)
|
||||
{
|
||||
dir = OrderDirection.Sell;
|
||||
orderType = OrderType.Limit;
|
||||
}
|
||||
|
||||
if (orderType == OrderType.Unspecified)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var req = new PostOrderRequest()
|
||||
{
|
||||
AccountId = tradeCommand.AccountId,
|
||||
InstrumentId = tradeCommand.Figi,
|
||||
Direction = dir,
|
||||
Price = tradeCommand.RecomendPrice ?? 0,
|
||||
OrderType = orderType,
|
||||
Quantity = tradeCommand.Count,
|
||||
ConfirmMarginTrade = tradeCommand.EnableMargin,
|
||||
};
|
||||
|
||||
_logger.LogWarning("Получена команда c id {commandId} на операцию с активом {figi}! Тип заявки сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
|
||||
tradeCommand.CommandId, req.InstrumentId, req.OrderType, req.Quantity, req.ConfirmMarginTrade);
|
||||
|
||||
var res = await _investApiClient.Orders.PostOrderAsync(req);
|
||||
|
||||
if ((tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitBuy
|
||||
|| tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitSell)
|
||||
&& tradeCommand.RecomendPrice.HasValue)
|
||||
{
|
||||
_tradeDataProvider.Accounts[tradeCommand.AccountId].Orders[res.OrderId] = new Models.AssetsAccounting.Order()
|
||||
{
|
||||
AccountId = tradeCommand.AccountId,
|
||||
Figi = tradeCommand.Figi,
|
||||
OrderId = res.OrderId,
|
||||
Ticker = _tradeDataProvider.GetTickerByFigi(tradeCommand.Figi),
|
||||
Count = res.LotsRequested,
|
||||
Direction = (DealDirection)(int)dir,
|
||||
ExpirationTime = DateTime.UtcNow.AddMinutes(2),
|
||||
OpenDate = DateTime.UtcNow,
|
||||
Price = tradeCommand.RecomendPrice.Value,
|
||||
};
|
||||
}
|
||||
_logger.LogWarning("Исполнена команда c id {commandId} на операцию с активом {figi}! Направление: {dir}; Число лотов: {lots};", tradeCommand.CommandId, res.Figi,
|
||||
res.Direction, res.LotsExecuted);
|
||||
}
|
||||
else if (tradeCommand.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitBuy && tradeCommand.RecomendPrice.HasValue)
|
||||
{
|
||||
dir = OrderDirection.Buy;
|
||||
orderType = OrderType.Limit;
|
||||
}
|
||||
|
||||
var req = new PostOrderRequest()
|
||||
{
|
||||
AccountId = tradeCommand.AccountId,
|
||||
InstrumentId = tradeCommand.Figi,
|
||||
Direction = dir,
|
||||
Price = tradeCommand.RecomendPrice ?? 0,
|
||||
OrderType = orderType,
|
||||
Quantity = tradeCommand.Count,
|
||||
ConfirmMarginTrade = tradeCommand.EnableMargin,
|
||||
};
|
||||
|
||||
_logger.LogWarning("Получена команда c id {commandId} на операцию с активом {figi}! Тип заявки сделки: {dir}; Количество активов: {count}; Разрешена ли маржиналка: {margin}",
|
||||
tradeCommand.CommandId, req.InstrumentId, req.OrderType, req.Quantity, req.ConfirmMarginTrade);
|
||||
|
||||
var res = await _investApiClient.Orders.PostOrderAsync(req);
|
||||
|
||||
_logger.LogWarning("Исполнена команда c id {commandId} на операцию с активом {figi}! Направление: {dir}; Число лотов: {lots}; цена: {price}", tradeCommand.CommandId, res.Figi,
|
||||
res.Direction, res.LotsExecuted, (decimal)res.ExecutedOrderPrice);
|
||||
//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)
|
||||
{
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace KLHZ.Trader.Service.Controllers
|
|||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task Run()
|
||||
public async Task Run(DateTime? startDate = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -34,7 +34,8 @@ namespace KLHZ.Trader.Service.Controllers
|
|||
//var figi1 = "BBG004730N88";
|
||||
var figi2 = "BBG004730N88";
|
||||
//var figi2 = "FUTIMOEXF000";
|
||||
var time1 = DateTime.UtcNow.AddDays(-17);
|
||||
var time1 = startDate?? DateTime.UtcNow.AddDays(-17);
|
||||
//var time1 = new DateTime(2025, 9, 4, 14, 0, 0, DateTimeKind.Utc);
|
||||
//var time2 = DateTime.UtcNow.AddMinutes(18);
|
||||
using var context1 = await _dbContextFactory.CreateDbContextAsync();
|
||||
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
|
|
Loading…
Reference in New Issue