фикс режима торговли
test / deploy_trader_prod (push) Successful in 5m4s Details

dev
vlad zverzhkhovskiy 2025-09-18 12:16:41 +03:00
parent 43ba83aa90
commit c3c6b52b7b
4 changed files with 237 additions and 55 deletions

View File

@ -125,5 +125,85 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
} }
return (res, bigWindowAv, smallWindowAv); return (res, bigWindowAv, smallWindowAv);
} }
public static (TradingEvent events, decimal bigWindowAv, decimal smallWindowAv) CheckByWindowAverageMean2(DateTime[] timestamps,
decimal[] prices, int size, int smallWindow, int bigWindow, TimeSpan timeForUptreandStart,
decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m)
{
var res = TradingEvent.None;
var bigWindowAv = 0m;
var smallWindowAv = 0m;
var s = 0;
var pricesForFinalComparison = new decimal[size];
var twavss = new decimal[size];
var twavbs = new decimal[size];
var times = new DateTime[size];
var crossings = new List<int>();
for (int shift = 0; shift < size - 1 && shift < prices.Length - 1; shift++)
{
s = shift;
var i2 = size - 1 - shift;
var i1 = size - 2 - shift;
var twavs = CalcTimeWindowAverageValue(timestamps, prices, smallWindow, shift);
var twavb = CalcTimeWindowAverageValue(timestamps, prices, bigWindow, shift);
pricesForFinalComparison[i2] = prices[prices.Length - 1 - shift];
if (shift == 0)
{
bigWindowAv = twavb.value;
smallWindowAv = twavs.value;
}
twavss[i2] = twavs.value;
twavbs[i2] = twavb.value;
times[i2] = twavb.time;
if (shift > 0)
{
var isCrossing = Lines.IsLinesCrossing(
times[i1 + 1],
times[i2 + 1],
twavss[i1 + 1],
twavss[i2 + 1],
twavbs[i1 + 1],
twavbs[i2 + 1]);
if (shift == 1 && !isCrossing.res) //если нет пересечения скользящих средний с окном 120 и 15 секунд между
//текущей и предыдущей точкой - можно не продолжать выполнение.
{
break;
}
if (isCrossing.res)
{
crossings.Add(i2);
if (crossings.Count == 2)
{
// если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта
if (twavss[size - 1] <= twavbs[size - 1] && twavss[size - 2] > twavbs[size - 2])
{
if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] >= uptrendEndingDetectionMeanfullStep
&& times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart)
{
res |= TradingEvent.UptrendEnd;
}
break;
}
// если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга и закрытия шорта
if (twavss[size - 1] >= twavbs[size - 1] && twavss[size - 2] < twavbs[size - 2])
{
if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] <= uptrendStartingDetectionMeanfullStep
&& times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart)
{
res |= TradingEvent.UptrendStart;
}
break;
}
}
}
}
}
return (res, bigWindowAv, smallWindowAv);
}
} }
} }

View File

@ -42,7 +42,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly decimal _accountCashPart; private readonly decimal _accountCashPart;
private readonly decimal _accountCashPartFutures; private readonly decimal _accountCashPartFutures;
private readonly string[] _tradingInstrumentsFigis = []; private readonly string[] _tradingInstrumentsFigis = [];
private readonly bool _isDebug = false;
private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>(); private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
private readonly Channel<IOrderbook> _ordersbookChannel = Channel.CreateUnbounded<IOrderbook>(); private readonly Channel<IOrderbook> _ordersbookChannel = Channel.CreateUnbounded<IOrderbook>();
@ -61,7 +60,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
_accountCashPart = options.Value.AccountCashPart; _accountCashPart = options.Value.AccountCashPart;
_accountCashPartFutures = options.Value.AccountCashPartFutures; _accountCashPartFutures = options.Value.AccountCashPartFutures;
_tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis; _tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis;
_isDebug = !options.Value.ExchangeDataRecievingEnabled;
foreach (var f in _tradingInstrumentsFigis) foreach (var f in _tradingInstrumentsFigis)
{ {
TradingModes[f] = TradingMode.None; TradingModes[f] = TradingMode.None;
@ -80,10 +78,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
_dataBus.AddChannel(nameof(Trader), _ordersbookChannel); _dataBus.AddChannel(nameof(Trader), _ordersbookChannel);
_ = ProcessPrices(); _ = ProcessPrices();
_ = ProcessOrders(); _ = ProcessOrders();
if (!_isDebug)
{
_ = TradingModeUpdatingWorker();
}
} }
public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(INewPrice message) public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(INewPrice message)
@ -171,12 +165,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
}; };
list.Clear(); list.Clear();
} }
}
try try
{ {
if (timesCache.TryGetValue(message.Figi, out var dt)) if (timesCache.TryGetValue(message.Figi, out var dt))
{ {
if ((message.Time - dt).TotalSeconds > 120) if ((message.Time - dt).TotalSeconds > 10)
{ {
timesCache[message.Figi] = message.Time; timesCache[message.Figi] = message.Time;
@ -192,7 +187,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
{ {
} }
}
if (TradingModes.TryGetValue(message.Figi, out var mode)) if (TradingModes.TryGetValue(message.Figi, out var mode))
{ {
await LogPrice(message, "trading_mode", (decimal)mode); await LogPrice(message, "trading_mode", (decimal)mode);
@ -249,12 +243,16 @@ namespace KLHZ.Trader.Core.Exchange.Services
} }
else if (TradingModes[message.Figi] == TradingMode.SlowDropping) else if (TradingModes[message.Figi] == TradingMode.SlowDropping)
{ {
await ProcessNewPriceIMOEXF_Dropping(data, state, message, windowMaxSize, 3); await ProcessNewPriceIMOEXF_Dropping(data, state, message, windowMaxSize, 2);
} }
else if (TradingModes[message.Figi] == TradingMode.Dropping) else if (TradingModes[message.Figi] == TradingMode.Dropping)
{ {
await ProcessNewPriceIMOEXF_Dropping(data, state, message, windowMaxSize, 6); await ProcessNewPriceIMOEXF_Dropping(data, state, message, windowMaxSize, 6);
} }
else if (TradingModes[message.Figi] == TradingMode.Growing)
{
await ProcessNewPriceIMOEXF_Growing(data, state, message, windowMaxSize);
}
else else
{ {
await ProcessNewPriceIMOEXF2(data, state, message, windowMaxSize); await ProcessNewPriceIMOEXF2(data, state, message, windowMaxSize);
@ -366,6 +364,19 @@ namespace KLHZ.Trader.Core.Exchange.Services
return resultMoveAvFull.events; return resultMoveAvFull.events;
} }
private async Task<TradingEvent> CheckByWindowAverageMean2((DateTime[] timestamps, decimal[] prices) data, int smallWindow, int bigWindow,
INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m)
{
var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean2(data.timestamps, data.prices,
windowMaxSize, smallWindow, bigWindow, TimeSpan.FromSeconds(20), uptrendStartingDetectionMeanfullStep, uptrendEndingDetectionMeanfullStep);
if (resultMoveAvFull.bigWindowAv != 0)
{
await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv);
await LogPrice(message, Constants.SmallWindowCrossingAverageProcessor, resultMoveAvFull.smallWindowAv);
}
return resultMoveAvFull.events;
}
private 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) INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m)
{ {
@ -471,10 +482,8 @@ namespace KLHZ.Trader.Core.Exchange.Services
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart
&& !LongOpeningStops.ContainsKey(message.Figi) && !LongOpeningStops.ContainsKey(message.Figi)
&& trendTask.Result.HasValue && trendTask.Result.HasValue
&& trendTask.Result.Value > -5
&& state == ExchangeState.Open && state == ExchangeState.Open
&& areasTask.Result.HasValue && areasTask.Result.HasValue
&& (areasTask.Result.Value >= 20 && areasTask.Result.Value < 75)
&& (positionTask.Result == ValueAmplitudePosition.LowerThenMediana) && (positionTask.Result == ValueAmplitudePosition.LowerThenMediana)
) )
{ {
@ -633,15 +642,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
return; return;
} }
var mavTask = CheckByWindowAverageMean(data, message, windowMaxSize, -1, 1m); var mavTask = CheckByWindowAverageMean2(data, 30, 180, message, windowMaxSize, 0, 0);
var ltTask = CheckByLocalTrends(data, message, windowMaxSize);
var positionTask = CheckPosition(message);
await Task.WhenAll(mavTask, ltTask, positionTask); await Task.WhenAll(mavTask);
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
var res = mavTask.Result | ltTask.Result; var res = mavTask.Result;
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart && (positionTask.Result == ValueAmplitudePosition.None || positionTask.Result == ValueAmplitudePosition.LowerThenMediana)) if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart)
{ {
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
{ {
@ -653,7 +660,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
{ {
if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart)) if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart))
{ {
if (RandomNumberGenerator.GetInt32(100) > 50 && await acc.Value.Lock(TimeSpan.FromSeconds(12))) if (await acc.Value.Lock(TimeSpan.FromSeconds(30)))
{ {
var command = new TradeCommand() var command = new TradeCommand()
{ {
@ -697,7 +704,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
Figi = message.Figi, Figi = message.Figi,
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitSell, CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.LimitSell,
Count = (long)asset.Count, Count = (long)asset.Count,
RecomendPrice = asset.BoughtPrice + 3, RecomendPrice = asset.BoughtPrice + 1.5m,
EnableMargin = false, EnableMargin = false,
}; };
await _dataBus.Broadcast(command); await _dataBus.Broadcast(command);
@ -707,6 +714,117 @@ namespace KLHZ.Trader.Core.Exchange.Services
} }
} }
private async Task ProcessNewPriceIMOEXF_Growing(
(DateTime[] timestamps, decimal[] prices) data,
ExchangeState state,
INewPrice message, int windowMaxSize)
{
if (data.timestamps.Length <= 4 || state != ExchangeState.Open)
{
return;
}
var mavTask = CheckByWindowAverageMean2(data, 30, 180, message, windowMaxSize, 0, 0);
await Task.WhenAll(mavTask);
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
var res = mavTask.Result;
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart)
{
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);
}
if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
{
if (!message.IsHistoricalData && BotModeSwitcher.CanSell())
{
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.Common && asset.Count > 0)
{
profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value,
GetComission(assetType), 1, false);
}
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)
{
LongClosingStops[message.Figi] = message.Time.AddSeconds(30);
var command = new TradeCommand()
{
AccountId = asset.AccountId,
Figi = message.Figi,
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell,
Count = (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.CloseLongReal, message, profit);
}
}
}
}
}
await LogDeclision(DeclisionTradeAction.CloseLong, message);
}
}
private async Task ProcessNewPriceIMOEXF_Dropping( private async Task ProcessNewPriceIMOEXF_Dropping(
(DateTime[] timestamps, decimal[] prices) data, (DateTime[] timestamps, decimal[] prices) data,
ExchangeState state, ExchangeState state,
@ -717,13 +835,12 @@ namespace KLHZ.Trader.Core.Exchange.Services
return; return;
} }
var mavTask = CheckByWindowAverageMean(data, message, windowMaxSize, -1, 1m); var mavTask = CheckByWindowAverageMean2(data, 30, 180, message, windowMaxSize, 0, 0);
var ltTask = CheckByLocalTrends(data, message, windowMaxSize);
var positionTask = CheckPosition(message); var positionTask = CheckPosition(message);
await Task.WhenAll(mavTask, ltTask, positionTask); await Task.WhenAll(mavTask, positionTask);
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
var res = mavTask.Result | ltTask.Result; var res = mavTask.Result;
if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd && (positionTask.Result != ValueAmplitudePosition.LowerThenMediana)) if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd && (positionTask.Result != ValueAmplitudePosition.LowerThenMediana))
{ {
@ -745,7 +862,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
Figi = message.Figi, Figi = message.Figi,
CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, CommandType = Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell,
Count = 1, Count = 1,
RecomendPrice = message.Value, RecomendPrice = message.Value - 0.5m,
ExchangeObject = acc.Value, ExchangeObject = acc.Value,
}; };
@ -977,24 +1094,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
return 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, internal static bool IsBuyAllowed(ManagedAccount account, decimal boutPrice, decimal count,
decimal accountCashPartFutures, decimal accountCashPart) decimal accountCashPartFutures, decimal accountCashPart)
{ {

View File

@ -98,6 +98,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
public async ValueTask AddData(INewPrice message, TimeSpan? clearingInterval = null) public async ValueTask AddData(INewPrice message, TimeSpan? clearingInterval = null)
{ {
if (message.Direction != 1) return;
if (_historyCash.TryGetValue(message.Figi, out var unit)) if (_historyCash.TryGetValue(message.Figi, out var unit))
{ {
if (clearingInterval.HasValue) if (clearingInterval.HasValue)
@ -215,7 +216,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
Ticker = c.Ticker, Ticker = c.Ticker,
Time = c.Time, Time = c.Time,
Value = c.Value, Value = c.Value,
IsHistoricalData = true IsHistoricalData = true,
Direction = c.Direction,
Count = c.Count,
}) })
.ToArrayAsync(); .ToArrayAsync();

View File

@ -103,7 +103,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
Ticker = _tradeDataProvider.GetTickerByFigi(tradeCommand.Figi), Ticker = _tradeDataProvider.GetTickerByFigi(tradeCommand.Figi),
Count = res.LotsRequested, Count = res.LotsRequested,
Direction = (DealDirection)(int)dir, Direction = (DealDirection)(int)dir,
ExpirationTime = DateTime.UtcNow.AddMinutes(2), ExpirationTime = DateTime.UtcNow.AddMinutes(10),
OpenDate = DateTime.UtcNow, OpenDate = DateTime.UtcNow,
Price = tradeCommand.RecomendPrice.Value, Price = tradeCommand.RecomendPrice.Value,
}; };