klhztrader/KLHZ.Trader.Core/Exchange/Services/Trader.cs

1209 lines
60 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums;
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Interfaces;
using KLHZ.Trader.Core.Exchange.Models.Configs;
using KLHZ.Trader.Core.Exchange.Models.Trading;
using KLHZ.Trader.Core.Exchange.Utils;
using KLHZ.Trader.Core.Math.Declisions.Dtos.FFT.Enums;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Channels;
using Tinkoff.InvestApi;
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
{
public class Trader : IHostedService
{
private readonly IDataBus _dataBus;
private readonly TraderDataProvider _tradeDataProvider;
private readonly PortfolioWrapper _portfolioWrapper;
private readonly ILogger<Trader> _logger;
private readonly ConcurrentDictionary<string, TradingMode> TradingModes = new();
private readonly ConcurrentDictionary<string, DateTime> LongOpeningStops = new();
private readonly ConcurrentDictionary<string, DateTime> ShortOpeningStops = new();
private readonly ConcurrentDictionary<string, DateTime> LongClosingStops = new();
private readonly ConcurrentDictionary<string, DateTime> ShortClosingStops = new();
private readonly ConcurrentDictionary<string, InstrumentSettings> Leverages = new();
private readonly decimal _futureComission;
private readonly decimal _shareComission;
private readonly decimal _accountCashPart;
private readonly decimal _accountCashPartFutures;
private readonly string[] _tradingInstrumentsFigis = [];
private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
private readonly Channel<ITradeCommand> _commands = Channel.CreateUnbounded<ITradeCommand>();
public Trader(
ILogger<Trader> logger,
IOptions<ExchangeConfig> options,
IDataBus dataBus,
PortfolioWrapper portfolioWrapper,
TraderDataProvider tradeDataProvider,
InvestApiClient investApiClient)
{
_portfolioWrapper = portfolioWrapper;
_tradeDataProvider = tradeDataProvider;
_logger = logger;
_dataBus = dataBus;
_futureComission = options.Value.FutureComission;
_shareComission = options.Value.ShareComission;
_accountCashPart = options.Value.AccountCashPart;
_accountCashPartFutures = options.Value.AccountCashPartFutures;
_tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis;
foreach (var f in _tradingInstrumentsFigis)
{
TradingModes[f] = TradingMode.None;
}
foreach (var lev in options.Value.InstrumentsSettings)
{
Leverages.TryAdd(lev.Figi, lev);
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
_dataBus.AddChannel(nameof(Trader), _pricesChannel);
//_dataBus.AddChannel(nameof(Trader), _ordersbookChannel);
_dataBus.AddChannel(nameof(Trader), _commands);
_ = ProcessPrices();
_ = ProcessCommands();
return Task.CompletedTask;
}
public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(INewPrice message, TimeSpan? windowSize = null)
{
var data2 = await _tradeDataProvider.GetData(message.Figi, windowSize??TimeSpan.FromHours(1));
//if (!data2.isFullIntervalExists)
//{
// data2 = await _tradeDataProvider.GetData(message.Figi, TimeSpan.FromHours(0.75));
//}
return data2;
}
private async ValueTask<(decimal smallWindow, decimal bigWindow)> GetWindowsSizes(INewPrice message)
{
var fftFull = await _tradeDataProvider.GetFFtResult(message.Figi + "_full");
if (!fftFull.IsEmpty)
{
var harms = fftFull.Harmonics.Skip(1).Take(fftFull.Harmonics.Length - 3).ToArray();
var sum = harms.Sum(h => h.Magnitude);
var sumtmp = 0f;
foreach (var h in harms)
{
sumtmp += h.Magnitude;
if (sumtmp / sum > 0.7f)
{
return ((decimal)(h.Period.TotalSeconds / 4), (decimal)(h.Period.TotalSeconds));
}
}
}
return (30m, 180m);
}
private async ValueTask<ValueAmplitudePosition> CheckHarmonicPosition((DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists) data, INewPrice message)
{
var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
var position = ValueAmplitudePosition.None;
var fft = await _tradeDataProvider.GetFFtResult(message.Figi);
var fftFull = await _tradeDataProvider.GetFFtResult(message.Figi + "_full");
//var highFreq = await _tradeDataProvider.GetFFtResult(message.Figi + "_high_freq");
//var lowFreq = await _tradeDataProvider.GetFFtResult(message.Figi + "_low_freq");
var step = message.IsHistoricalData ? 40 : 5;
if (fft.IsEmpty || (currentTime - fft.LastTime).TotalSeconds > step)
{
if (data.isFullIntervalExists)
{
var interpolatedData = SignalProcessing.InterpolateData(data.timestamps, data.prices, TimeSpan.FromSeconds(5));
fftFull = FFT.Analyze(interpolatedData.timestamps, interpolatedData.values, message.Figi+"_full", TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(30));
//fft = FFT.ReAnalyze(fftFull, message.Figi, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(60));
//highFreq = FFT.ReAnalyze(fftFull, message.Figi + "_low_freq", TimeSpan.FromMinutes(20), TimeSpan.FromMinutes(60));
//lowFreq = FFT.ReAnalyze(fftFull, message.Figi + "_high_freq", TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(20));
//var tmp = FFT.GetMainHarmonic(interpolatedData.timestamps, interpolatedData.values, "mainHarm", TimeSpan.FromMinutes(20));
//await _tradeDataProvider.SetFFtResult(fft);
await _tradeDataProvider.SetFFtResult(fftFull);
//await _tradeDataProvider.SetFFtResult(lowFreq);
//await _tradeDataProvider.SetFFtResult(highFreq);
}
//var highFreqData = await GetData(message, TimeSpan.FromMinutes(120));
//if (highFreqData.isFullIntervalExists)
//{
// var interpolatehighFreqData = SignalProcessing.InterpolateData(data.timestamps, data.prices, TimeSpan.FromSeconds(5));
// highFreq = FFT.Analyze(interpolatehighFreqData.timestamps, interpolatehighFreqData.values, message.Figi + "_high_freq", TimeSpan.FromSeconds(20), TimeSpan.FromMinutes(120));
// await _tradeDataProvider.SetFFtResult(highFreq);
//}
}
position = FFT.Check(fftFull, message.Time);
if (position == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.UpperThen30Decil)
{
await LogPrice(message, "upper30percent", message.Value);
}
if (position == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.LowerThenMedianaGrowing)
{
await LogPrice(message, "lower30percent", message.Value);
}
//var hposition = FFT.CheckExtremums(highFreq, message.Time);
//if (hposition == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.UpperThen30Decil)
//{
// await LogPrice(message, "high_freq_high", message.Value);
//}
//if (hposition == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.LowerThen30Decil)
//{
// await LogPrice(message, "high_freq_low", message.Value);
//}
//var gposition = FFT.CheckSign(highFreq, message.Time);
//if (gposition == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.Growing)
//{
// await LogPrice(message, "growing", message.Value);
//}
//if (gposition == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.Falling)
//{
// await LogPrice(message, "falling", message.Value);
//}
//var lposition = FFT.CheckExtremums(lowFreq, message.Time);
//if (lposition == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.UpperThen30Decil)
//{
// await LogPrice(message, "low_freq_high", message.Value);
//}
//if (lposition == Math.Declisions.Dtos.FFT.Enums.ValueAmplitudePosition.LowerThen30Decil)
//{
// await LogPrice(message, "low_freq_low", message.Value);
//}
return position;
}
private async Task ProcessCommands()
{
while (await _commands.Reader.WaitToReadAsync())
{
var command = await _commands.Reader.ReadAsync();
try
{
if (command.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.OpenLong
|| command.CommandType == Contracts.Messaging.Dtos.Enums.TradeCommandType.OpenShort)
{
var fakeMessage = new NewPriceMessage() { Figi = command.Figi, Ticker = "", Count = command.Count, Direction = 1, IsHistoricalData = false, Time = DateTime.UtcNow, Value = command.RecomendPrice ?? 0m };
var positionType = command.CommandType == TradeCommandType.OpenLong ? PositionType.Long : PositionType.Short;
var stops = GetStops(fakeMessage, positionType);
var accounts = _portfolioWrapper.Accounts
.Where(a => !a.Value.Assets.ContainsKey(command.Figi))
.Take(1)
.Select(a => a.Value)
.ToArray();
await OpenPositions(accounts, fakeMessage, positionType, stops.stopLoss, stops.takeProfit, System.Math.Abs(command.Count));
}
else
{
var fakeMessage = new NewPriceMessage() { Figi = command.Figi, Ticker = "", Count = command.Count, Direction = 1, IsHistoricalData = false, Time = DateTime.UtcNow, Value = command.RecomendPrice ?? 0m };
var assetsForClose = _portfolioWrapper.Accounts
.SelectMany(a => a.Value.Assets.Values)
.Where(a => a.Figi == fakeMessage.Figi)
.ToArray();
await ClosePositions(assetsForClose, fakeMessage, false);
}
}
catch (Exception ex)
{
}
}
}
private async Task ProcessPrices()
{
var pricesCache1 = new Dictionary<string, List<INewPrice>>();
var pricesCache2 = new Dictionary<string, List<INewPrice>>();
var timesCache = new Dictionary<string, DateTime>();
while (await _pricesChannel.Reader.WaitToReadAsync())
{
var message = await _pricesChannel.Reader.ReadAsync();
var changeMods = GetInitDict(0);
try
{
#region Ускорение обработки исторических данных при отладке
if (message.Direction == 1)
{
if (!pricesCache1.TryGetValue(message.Figi, out var list))
{
list = new List<INewPrice>();
pricesCache1[message.Figi] = list;
}
list.Add(message);
if ((list.Last().Time - list.First().Time).TotalSeconds < 0.5)
{
list.Add(message);
continue;
}
else
{
message = new PriceChange()
{
Figi = message.Figi,
Ticker = message.Ticker,
Count = message.Count,
Direction = message.Direction,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Value = list.Sum(l => l.Value) / list.Count
};
list.Clear();
}
}
if (message.Direction == 2)
{
if (!pricesCache2.TryGetValue(message.Figi, out var list))
{
list = new List<INewPrice>();
pricesCache2[message.Figi] = list;
}
list.Add(message);
if ((list.Last().Time - list.First().Time).TotalSeconds < 0.5)
{
list.Add(message);
continue;
}
else
{
message = new PriceChange()
{
Figi = message.Figi,
Ticker = message.Ticker,
Count = message.Count,
Direction = message.Direction,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Value = list.Sum(l => l.Value) / list.Count
};
list.Clear();
}
}
#endregion
#region Подсчёт торгового баланса по сберу и IMOEXF
if (message.Figi == "BBG004730N88" || message.Figi == "FUTIMOEXF000")
{
if (message.Direction == 1)
{
await _tradeDataProvider.AddDataTo20SecondsWindowCache(message.Figi, "1", new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = message.Count
});
await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._5minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = (decimal)message.Count
});
await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, Constants._15minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = (decimal)message.Count
});
await _tradeDataProvider.AddDataTo1MinuteWindowCache(message.Figi, Constants._1minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = (decimal)message.Count
});
}
if (message.Direction == 2)
{
await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._5minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = (decimal)message.Count
});
await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, Constants._15minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = (decimal)message.Count
});
await _tradeDataProvider.AddDataTo1MinuteWindowCache(message.Figi, Constants._1minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = (decimal)message.Count
});
await _tradeDataProvider.AddDataTo20SecondsWindowCache(message.Figi, "2", new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = message.Count
});
}
var buys = await _tradeDataProvider.GetDataFrom20SecondsWindowCache(message.Figi, "1");
var sells = await _tradeDataProvider.GetDataFrom20SecondsWindowCache(message.Figi, "2");
var buysSpeed = buys.Sum(p => p.Value) / 20;
var sellsSpeed = sells.Sum(p => p.Value) / 20;
var orderBook = _tradeDataProvider.Orderbooks[message.Figi];
if (orderBook.Asks.Length>3 && orderBook.Bids.Length>3)
{
var asks = (decimal)(orderBook.Asks[0].Count + orderBook.Asks[1].Count + orderBook.Asks[2].Count);
//var asks = (decimal)(orderBook.Asks[0].Count + orderBook.Asks[1].Count + orderBook.Asks[2].Count + orderBook.Asks[3].Count);
var bids = (decimal)(orderBook.Bids[0].Count + orderBook.Bids[1].Count + orderBook.Bids[2].Count);
//var bids = (decimal)(orderBook.Bids[0].Count + orderBook.Bids[1].Count + orderBook.Bids[2].Count + orderBook.Bids[3].Count);
if (buysSpeed > 0 && sellsSpeed > 0)
{
await LogPrice(message, "speed_relation", (sellsSpeed / (sellsSpeed + buysSpeed)));
}
//var diff = buysSpeed - sellsSpeed;
//await LogPrice(message, "speed_diff", diff);
//await LogPrice(message, "stabling", (asks+bids)/(sellsSpeed+buysSpeed));
//if (buysSpeed > 0)
//{
// var asksLifetime = asks / buysSpeed;
// if (asksLifetime > 600) asksLifetime = 600;
// var asksLifetime2 = diff > 0?System.Math.Abs( asks / diff):0;
// await LogPrice(message, "asks_lifetime", asksLifetime);
// await LogPrice(message, "asks_lifetime2", asksLifetime2);
// await LogPrice(message, "asks_lifetime2", asksLifetime2);
// await LogPrice(message, "buys_speed", buysSpeed);
//}
//if (sellsSpeed > 0)
//{
// var bidsLifetime = bids / sellsSpeed;
// if (bidsLifetime > 600) bidsLifetime = 600;
// var bidsLifetime2 = diff < 0 ? System.Math.Abs(bids / diff) : 0;
// await LogPrice(message, "bids_lifetime", bidsLifetime);
// await LogPrice(message, "bids_lifetime2", System.Math.Abs(bidsLifetime2));
// await LogPrice(message, "sells_speed", sellsSpeed);
//}
//var buys5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._5minBuyCacheKey);
//var sells5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._5minSellCacheKey);
//var buysSpeed5min = buys5min.Sum(p => p.Value) / 300;
//var sellsSpeed5min = sells5min.Sum(p => p.Value) / 300;
//var diff5min = buysSpeed5min - sellsSpeed5min;
//await LogPrice(message, "speed_diff_5min", diff5min);
//var buys1min = await _tradeDataProvider.GetDataFrom1MinuteWindowCache(message.Figi, Constants._1minBuyCacheKey);
//var sells1min = await _tradeDataProvider.GetDataFrom1MinuteWindowCache(message.Figi, Constants._1minSellCacheKey);
//var buysSpeed1min = buys1min.Sum(p => p.Value) / 60;
//var sellsSpeed1min = sells1min.Sum(p => p.Value) / 60;
//var diff1min = buysSpeed1min - sellsSpeed1min;
//await LogPrice(message, "speed_diff_1min", diff1min);
}
}
#endregion
if (_tradingInstrumentsFigis.Contains(message.Figi) && message.Figi == "FUTIMOEXF000" && message.Direction == 1)
{
var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
try
{
if (timesCache.TryGetValue(message.Figi, out var dt))
{
if ((message.Time - dt).TotalSeconds > 10)
{
timesCache[message.Figi] = message.Time;
var newMod = await CalcTradingMode(message);
if (TradingModes.TryGetValue(message.Figi, out var oldMod))
{
if ((oldMod == TradingMode.Growing || oldMod == TradingMode.Stable)
&& oldMod != newMod)
{
changeMods[TradingEvent.UptrendEnd] = Constants.PowerUppingCoefficient;
}
if ((oldMod == TradingMode.Dropping || oldMod == TradingMode.SlowDropping)
&& oldMod != newMod)
{
changeMods[TradingEvent.DowntrendEnd] = Constants.PowerUppingCoefficient;
}
if (newMod == TradingMode.Growing && newMod != oldMod && !LongOpeningStops.ContainsKey(message.Figi))
{
//changeMods[TradingEvent.UptrendStart] = Constants.PowerUppingCoefficient;
}
if (newMod == TradingMode.Dropping && newMod != oldMod && !ShortOpeningStops.ContainsKey(message.Figi))
{
//changeMods[TradingEvent.DowntrendStart] = Constants.PowerUppingCoefficient;
}
TradingModes[message.Figi] = newMod;
if (oldMod != newMod)
{
var accountForStopsChanging = _portfolioWrapper.Accounts
.Where(a => a.Value.Assets.ContainsKey(message.Figi))
.ToArray();
foreach (var account in accountForStopsChanging)
{
if (account.Value.Assets.TryGetValue(message.Figi, out var asset))
{
var stops = GetStops(message, asset.Count > 0 ? PositionType.Long : PositionType.Short);
if (!message.IsHistoricalData)
{
await account.Value.ResetStops(message.Figi, stops.stopLoss, stops.takeProfit);
if (asset.Count < 0)
{
await LogDeclision(DeclisionTradeAction.ResetStopsShort, asset.BoughtPrice - stops.takeProfit, message.Time.AddMilliseconds(-100), message);
await LogDeclision(DeclisionTradeAction.ResetStopsShort, asset.BoughtPrice + stops.stopLoss, message.Time.AddMilliseconds(100), message);
}
else
{
await LogDeclision(DeclisionTradeAction.ResetStopsLong, asset.BoughtPrice + stops.takeProfit, message.Time.AddMilliseconds(-100), message);
await LogDeclision(DeclisionTradeAction.ResetStopsLong, asset.BoughtPrice - stops.stopLoss, message.Time.AddMilliseconds(100), message);
}
}
}
}
}
}
}
}
else
{
timesCache[message.Figi] = message.Time;
}
}
catch (Exception ex)
{
}
if (TradingModes.TryGetValue(message.Figi, out var mode))
{
await LogPrice(message, "trading_mode", (decimal)mode);
}
try
{
if (message.Direction != 1) continue;
await _tradeDataProvider.AddData(message);
ProcessStops(message, currentTime);
var windowMaxSize = 2000;
var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize);
var state = ExchangeScheduler.GetCurrentState(message.Time);
await ProcessNewPriceIMOEXF3(data, state, message, windowMaxSize, changeMods.ToImmutableDictionary());
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка при боработке новой цены IMOEXF");
}
}
}
catch (Exception e)
{
}
}
}
private async Task<ImmutableDictionary<TradingEvent, decimal>> GetSpeedResultantMods(INewPrice message)
{
var res = GetInitDict(1);
var buys5min = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, Constants._15minBuyCacheKey);
var sells5min = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, Constants._15minSellCacheKey);
var buysSpeed5min = buys5min.Sum(p => p.Value) / 300;
var sellsSpeed5min = sells5min.Sum(p => p.Value) / 300;
var diff5min = buysSpeed5min - sellsSpeed5min;
await LogPrice(message, "speed_diff_15min", diff5min);
if (diff5min < 0)
{
res[TradingEvent.UptrendStart] = Constants.BlockingCoefficient;
}
if (diff5min > 0)
{
res[TradingEvent.DowntrendStart] = Constants.BlockingCoefficient;
}
if (diff5min > 6)
{
res[TradingEvent.UptrendEnd] = Constants.BlockingCoefficient;
}
if (diff5min < -6)
{
res[TradingEvent.DowntrendEnd] = Constants.BlockingCoefficient;
}
return res.ToImmutableDictionary();
}
private async Task<Dictionary<TradingEvent, decimal>> GetWindowAverageStartData((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, uptrendStartingDetectionMeanfullStep, uptrendEndingDetectionMeanfullStep);
if (resultMoveAvFull.bigWindowAv != 0)
{
await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv);
await LogPrice(message, Constants.SmallWindowCrossingAverageProcessor, resultMoveAvFull.smallWindowAv);
}
var res = GetInitDict(0);
res[TradingEvent.DowntrendEnd] = Constants.PowerLowingCoefficient;
res[TradingEvent.UptrendEnd] = Constants.PowerLowingCoefficient;
if ((resultMoveAvFull.events & TradingEvent.UptrendStart) == TradingEvent.UptrendStart)
{
res[TradingEvent.UptrendStart] = Constants.PowerUppingCoefficient;
res[TradingEvent.DowntrendEnd] = Constants.PowerUppingCoefficient;
}
if ((resultMoveAvFull.events & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
{
res[TradingEvent.UptrendEnd] = Constants.PowerUppingCoefficient;
res[TradingEvent.DowntrendStart] = Constants.PowerUppingCoefficient;
}
return res;
}
private async Task<decimal?> GetAreasRelation((DateTime[] timestamps, decimal[] prices) data, INewPrice message)
{
var areasRel = -1m;
if (ShapeAreaCalculator.TryGetAreasRelation(data.timestamps, data.prices, message.Value, Constants.AreasRelationWindow, out var rel))
{
await _tradeDataProvider.AddDataTo1MinuteWindowCache(message.Figi, Constants._1minCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value = (decimal)rel
});
var areas = await _tradeDataProvider.GetDataFrom1MinuteWindowCache(message.Figi, Constants._1minCacheKey);
areasRel = (decimal)areas.Sum(a => a.Value) / areas.Length;
await LogPrice(message, Constants.AreasRelationProcessor, areasRel);
return areasRel > 0 ? areasRel : null;
}
return null;
}
private async Task<ValueAmplitudePosition> CheckHarmonicPosition(INewPrice message)
{
var data2 = await GetData(message);
var position = await CheckHarmonicPosition(data2, message);
return position;
}
private async Task ClosePositions(Asset[] assets, INewPrice message, bool withProfitOnly = true)
{
var loggedDeclisions = 0;
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
var assetsForClose = new List<Asset>();
var price = message.Value;
if (price == 0)
{
price = await _tradeDataProvider.GetLastPrice(message.Figi);
}
price = System.Math.Round(price, 2);
var messages = new List<string>();
foreach (var asset in assets)
{
if (withProfitOnly)
{
var profit = 0m;
if (assetType == AssetType.Futures)
{
profit = TradingCalculator.CaclProfit(asset.BoughtPrice, price,
GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0);
}
if (profit > 0)
{
profit = System.Math.Round(profit, 2);
assetsForClose.Add(asset);
messages.Add($"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{price}, профит {profit}");
if (loggedDeclisions == 0)
{
loggedDeclisions++;
await LogDeclision(asset.Count < 0 ? DeclisionTradeAction.CloseShortReal : DeclisionTradeAction.CloseLongReal, message, profit);
}
}
}
else
{
messages.Add($"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{price}");
assetsForClose.Add(asset);
}
}
var tasks = assetsForClose.Select(asset => _portfolioWrapper.Accounts[asset.AccountId].ClosePosition(message.Figi));
await Task.WhenAll(tasks);
foreach (var mess in messages)
{
await _dataBus.Broadcast(new MessageForAdmin() { Text = mess });
}
}
private async Task OpenPositions(IManagedAccount[] accounts, INewPrice message, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1)
{
var loggedDeclisions = 0;
var sign = positionType == PositionType.Long ? 1 : 1;
foreach (var acc in accounts)
{
if (IsOperationAllowed(acc, message.Value, 1, _accountCashPartFutures, _accountCashPart))
{
await acc.OpenPosition(message.Figi, positionType, stopLossShift, takeProfitShift, count);
await _dataBus.Broadcast(new MessageForAdmin()
{
Text = $"Открываю позицию {message.Figi} ({(positionType == PositionType.Long ? "лонг" : "шорт")}) " +
$"на счёте {acc.AccountName}. Количество {(positionType == PositionType.Long ? "" : "-")}{count}, " +
$"цена ~{System.Math.Round(message.Value, 2)}. Стоп лосс: {(positionType == PositionType.Long ? "-" : "+")}{stopLossShift}. " +
$"Тейк профит: {(positionType == PositionType.Long ? "+" : "-")}{takeProfitShift}"
});
}
if (loggedDeclisions == 0)
{
await LogDeclision(DeclisionTradeAction.OpenLongReal, message);
LongOpeningStops[message.Figi] = message.Time.AddMinutes(1);
loggedDeclisions++;
}
}
}
private async Task ProcessNewPriceIMOEXF3((DateTime[] timestamps, decimal[] prices) data,
ExchangeState state,
INewPrice message, int windowMaxSize, ImmutableDictionary<TradingEvent, decimal> changeModeData)
{
if (data.timestamps.Length <= 4)
{
return;
}
var windows = await GetWindowsSizes(message);
//var resTask1 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, -2m, 2m,3);
var resTask1 = GetWindowAverageStartData(data, (int)windows.smallWindow, (int)windows.bigWindow, message, windowMaxSize, -1m, 1m);
////var resTask3 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, 0, 0,0.7m);
var getFFTModsTask = GetFFTMods(message);
var getLocalTrendsModsTask = GetLocalTrendsMods(data, message);
var getSellsDiffsModsTask = GetSellsDiffsMods(message);
var getTradingModeModsTask = GetTradingModeMods(message);
var getSpeedResultantModsTask = GetSpeedResultantMods(message);
await Task.WhenAll(resTask1, getFFTModsTask, getSellsDiffsModsTask, getTradingModeModsTask, getLocalTrendsModsTask, getSpeedResultantModsTask);
//var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
//if (resTask1.Result[TradingEvent.UptrendStart] >= 1)
//{
//}
var result = resTask1.Result;
//result = MergeResults(result, resTask2.Result.ToImmutableDictionary());
//result = MergeResults(result, resTask3.Result.ToImmutableDictionary());
result = MergeResultsMax(result, changeModeData);
result = MergeResultsMax(result, getLocalTrendsModsTask.Result);
result = MergeResultsMult(result, getFFTModsTask.Result);
result = MergeResultsMult(result, getSellsDiffsModsTask.Result);
result = MergeResultsMult(result, getTradingModeModsTask.Result);
result = MergeResultsMult(result, getSpeedResultantModsTask.Result);
if (result[TradingEvent.UptrendStart] >= Constants.UppingCoefficient
&& !LongOpeningStops.ContainsKey(message.Figi)
&& state == ExchangeState.Open
)
{
var stops = GetStops(message, PositionType.Long);
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
{
var accounts = _portfolioWrapper.Accounts
.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
.Take(1)
.Select(a => a.Value)
.ToArray();
await OpenPositions(accounts, message, PositionType.Long, stops.stopLoss, stops.takeProfit, 1);
LongOpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(1);
}
await LogDeclision(DeclisionTradeAction.OpenLong, message.Value, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message);
await LogDeclision(DeclisionTradeAction.ResetStopsLong, message.Value + stops.takeProfit, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message);
await LogDeclision(DeclisionTradeAction.ResetStopsLong, message.Value - stops.stopLoss, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message);
}
if (result[TradingEvent.DowntrendStart] >= Constants.UppingCoefficient
&& !ShortOpeningStops.ContainsKey(message.Figi)
&& state == ExchangeState.Open
)
{
var stops = GetStops(message, PositionType.Short);
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
{
var accounts = _portfolioWrapper.Accounts
.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
.Take(1)
.Select(a => a.Value)
.ToArray();
await OpenPositions(accounts, message, PositionType.Short, stops.stopLoss, stops.takeProfit, 1);
ShortOpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(1);
}
await LogDeclision(DeclisionTradeAction.OpenShort, message.Value, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message);
await LogDeclision(DeclisionTradeAction.ResetStopsShort, message.Value - stops.takeProfit, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message);
await LogDeclision(DeclisionTradeAction.ResetStopsShort, message.Value + stops.stopLoss, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message);
}
if (result[TradingEvent.UptrendEnd] >= Constants.UppingCoefficient)
{
if (!message.IsHistoricalData && BotModeSwitcher.CanSell())
{
var assetsForClose = _portfolioWrapper.Accounts
.SelectMany(a => a.Value.Assets.Values)
.Where(a => a.Figi == message.Figi && a.Count > 0)
.ToArray();
await ClosePositions(assetsForClose, message);
}
await LogDeclision(DeclisionTradeAction.CloseLong, message.Value, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message);
}
if (result[TradingEvent.DowntrendEnd] >= Constants.UppingCoefficient)
{
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
{
var assetsForClose = _portfolioWrapper.Accounts
.SelectMany(a => a.Value.Assets.Values)
.Where(a => a.Figi == message.Figi && a.Count < 0)
.ToArray();
await ClosePositions(assetsForClose, message);
}
await LogDeclision(DeclisionTradeAction.CloseShort, message.Value, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message);
}
}
private void ProcessStops(INewPrice message, DateTime currentTime)
{
if (LongOpeningStops.TryGetValue(message.Figi, out var dt))
{
if (dt < currentTime)
{
LongOpeningStops.TryRemove(message.Figi, out _);
}
}
if (ShortClosingStops.TryGetValue(message.Figi, out var dt2))
{
if (dt2 < currentTime)
{
ShortClosingStops.TryRemove(message.Figi, out _);
}
}
if (LongClosingStops.TryGetValue(message.Figi, out var dt3))
{
if (dt3 < currentTime)
{
LongClosingStops.TryRemove(message.Figi, out _);
}
}
if (ShortOpeningStops.TryGetValue(message.Figi, out var dt4))
{
if (dt4 < currentTime)
{
ShortOpeningStops.TryRemove(message.Figi, out _);
}
}
}
private async Task LogPrice(INewPrice message, string processor, decimal value)
{
await _tradeDataProvider.LogPrice(new ProcessedPrice()
{
Figi = message.Figi,
Ticker = message.Ticker,
Processor = processor,
Time = message.Time,
Value = value,
}, false);
}
private async Task LogDeclision(DeclisionTradeAction action, INewPrice message, decimal? profit = null)
{
await _tradeDataProvider.LogDeclision(new Declision()
{
AccountId = string.Empty,
Figi = message.Figi,
Ticker = message.Ticker,
Value = profit,
Price = message.Value,
Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
Action = action,
}, false);
}
private async Task LogDeclision(DeclisionTradeAction action, decimal price, DateTime time, INewPrice message)
{
await _tradeDataProvider.LogDeclision(new Declision()
{
AccountId = string.Empty,
Figi = message.Figi,
Ticker = message.Ticker,
Value = price,
Price = price,
Time = time,
Action = action,
}, false);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private decimal GetComission(AssetType assetType)
{
if (assetType == AssetType.Common)
{
return _shareComission;
}
else if (assetType == AssetType.Futures)
{
return _futureComission;
}
else
{
return 0;
}
}
private decimal GetLeverage(string figi, bool isShort)
{
var res = 1m;
if (Leverages.TryGetValue(figi, out var leverage))
{
res = isShort ? leverage.ShortLeverage : leverage.LongLeverage;
}
return res;
}
private async Task<TradingMode> CalcTradingMode(string figi)
{
var res = TradingMode.None;
var largeData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(45));
var smallData = await _tradeDataProvider.GetData(figi, TimeSpan.FromMinutes(10));
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;
}
if (smallDataRes > 7)
{
res = TradingMode.Growing;
}
if (smallDataRes < - 7)
{
res = TradingMode.Dropping;
}
}
}
return res;
}
private async Task<TradingMode> CalcTradingMode(INewPrice message)
{
var res = await CalcTradingMode(message.Figi);
return res;
}
private (decimal stopLoss, decimal takeProfit) GetStops(INewPrice message, PositionType type)
{
var mode = TradingModes[message.Figi];
decimal stopLossShift = 15;
decimal takeProfitShift = 6;
if (mode == TradingMode.Growing && type == PositionType.Long)
{
takeProfitShift = 15;
}
if (mode == TradingMode.Growing && type == PositionType.Short)
{
stopLossShift = 10;
takeProfitShift = 2;
}
if (mode == TradingMode.Stable && type == PositionType.Long)
{
takeProfitShift = 2.5m;
}
if (mode == TradingMode.Stable && type == PositionType.Short)
{
takeProfitShift = 2.5m;
stopLossShift = 10;
}
if (mode == TradingMode.SlowDropping && type == PositionType.Short)
{
}
if (mode == TradingMode.SlowDropping && type == PositionType.Long)
{
takeProfitShift = 1.5m;
stopLossShift = 10;
}
if (mode == TradingMode.Dropping && type == PositionType.Short)
{
takeProfitShift = 15;
}
if (mode == TradingMode.Dropping && type == PositionType.Long)
{
stopLossShift = 10;
takeProfitShift = 1.5m;
}
return (stopLossShift, takeProfitShift);
}
private async Task<ImmutableDictionary<TradingEvent, decimal>> GetFFTMods(INewPrice message)
{
var res = GetInitDict(1);
var position = await CheckHarmonicPosition(message);
if (position == ValueAmplitudePosition.LowerThenMedianaGrowing)
{
//res[TradingEvent.UptrendStart] = Constants.UppingCoefficient;
res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient;
//res[TradingEvent.UptrendEnd] = Constants.LowingCoefficient;
res[TradingEvent.DowntrendStart] = Constants.LowingCoefficient;
}
if (position == ValueAmplitudePosition.UpperThen30Decil)
{
res[TradingEvent.UptrendStart] = Constants.LowingCoefficient;
//res[TradingEvent.DowntrendEnd] = Constants.LowingCoefficient;
//res[TradingEvent.UptrendEnd] = Constants.UppingCoefficient;
res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient;
}
return res.ToImmutableDictionary();
}
private async Task<ImmutableDictionary<TradingEvent, decimal>> GetAreasMods((DateTime[] timestamps, decimal[] prices) data, INewPrice message)
{
var res = GetInitDict(1);
var areas = await GetAreasRelation(data, message);
if (areas.HasValue && areas.Value > 0.2m && areas.Value <= 0.8m)
{
//res[TradingEvent.UptrendStart] = Constants.PowerLowingCoefficient;
}
if (areas.HasValue && areas.Value > 0.8m)
{
//res[TradingEvent.DowntrendStart] = Constants.UppingCoefficient;
//res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient;
}
return res.ToImmutableDictionary();
}
private Task<ImmutableDictionary<TradingEvent, decimal>> GetLocalTrendsMods((DateTime[] timestamps, decimal[] prices) data, INewPrice message)
{
var res = GetInitDict(0);
if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15), 0.5, out var localTrends))
{
if ((localTrends & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
{
res[TradingEvent.UptrendEnd] = Constants.PowerUppingCoefficient;
}
if ((localTrends & TradingEvent.DowntrendEnd) == TradingEvent.DowntrendEnd)
{
res[TradingEvent.DowntrendEnd] = Constants.PowerUppingCoefficient;
}
}
return Task.FromResult(res.ToImmutableDictionary());
}
private async Task<ImmutableDictionary<TradingEvent, decimal>> GetSellsDiffsMods(INewPrice message)
{
var res = GetInitDict(1);
var sberSells5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._5minSellCacheKey);
var sberBuys5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._5minBuyCacheKey);
var sberSells1min = await _tradeDataProvider.GetDataFrom1MinuteWindowCache("BBG004730N88", Constants._1minSellCacheKey);
var sberBuys1min = await _tradeDataProvider.GetDataFrom1MinuteWindowCache("BBG004730N88", Constants._1minBuyCacheKey);
var sells5min = sberSells5min.Sum(s => s.Value);
var buys5min = sberBuys5min.Sum(s => s.Value);
var sells1min = sberSells1min.Sum(s => s.Value);
var buys1min = sberBuys1min.Sum(s => s.Value);
var su = sells5min + buys5min;
var uptrendEndMode = 1m;
var downtrendEndMode = 1m;
var downstartMode = 1m;
var uptrendStartMode = 1m;
if (su != 0)
{
var trades_rel = (sells5min / su - 0.5m) * 2;
var bys_rel = buys1min / su;
var sells_rel = sells1min / su;
await LogPrice(message, "trades_rel", trades_rel);
await LogPrice(message, "bys_rel", bys_rel);
await LogPrice(message, "sells_rel", sells_rel);
if (trades_rel > 0.7m)
{
//uptrendStartMode *= Constants.LowingCoefficient;
}
if (System.Math.Abs(bys_rel) > 0.6m || System.Math.Abs(sells_rel) > 0.6m)
{
uptrendStartMode *= Constants.PowerUppingCoefficient;
downstartMode *= Constants.PowerUppingCoefficient;
uptrendEndMode *= Constants.BlockingCoefficient;
downtrendEndMode *= Constants.BlockingCoefficient;
}
else if (System.Math.Abs(bys_rel) > 0.3m || System.Math.Abs(sells_rel) > 0.3m)
{
uptrendStartMode *= Constants.UppingCoefficient;
downstartMode *= Constants.UppingCoefficient;
uptrendEndMode *= Constants.LowingCoefficient;
downtrendEndMode *= Constants.LowingCoefficient;
}
//else if (System.Math.Abs(bys_rel) <= 0.2m && System.Math.Abs(sells_rel) <= 0.2m)
//{
// uptrendEndMode *= Constants.UppingCoefficient;
// downtrendEndMode *= Constants.UppingCoefficient;
//}
res[TradingEvent.UptrendStart] = uptrendStartMode;
res[TradingEvent.UptrendEnd] = uptrendEndMode;
res[TradingEvent.DowntrendEnd] = downtrendEndMode;
res[TradingEvent.DowntrendStart] = downstartMode;
}
return res.ToImmutableDictionary();
}
private Task<ImmutableDictionary<TradingEvent, decimal>> GetTradingModeMods(INewPrice message)
{
var res = GetInitDict(1);
var mode = TradingModes[message.Figi];
if (mode == TradingMode.None)
{
//res[TradingEvent.UptrendEnd] = Constants.UppingCoefficient;
//res[TradingEvent.UptrendStart] = 1;
res[TradingEvent.DowntrendStart] = Constants.LowingCoefficient;
//res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient;
}
if (mode == TradingMode.Growing)
{
res[TradingEvent.UptrendEnd] = Constants.LowingCoefficient;
res[TradingEvent.UptrendStart] = Constants.UppingCoefficient;
res[TradingEvent.DowntrendStart] = Constants.BlockingCoefficient;
res[TradingEvent.DowntrendEnd] = Constants.PowerUppingCoefficient;
}
if (mode == TradingMode.Stable)
{
//res[TradingEvent.UptrendEnd] = 1;
res[TradingEvent.UptrendStart] = Constants.LowingCoefficient;
//res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient;
res[TradingEvent.DowntrendStart] = Constants.LowingCoefficient;
}
if (mode == TradingMode.SlowDropping)
{
//res[TradingEvent.UptrendEnd] = Constants.PowerUppingCoefficient;
//res[TradingEvent.UptrendStart] = Constants.LowingCoefficient;
res[TradingEvent.DowntrendStart] = Constants.UppingCoefficient;
//res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient;
}
if (mode == TradingMode.Dropping)
{
res[TradingEvent.UptrendEnd] = Constants.PowerUppingCoefficient;
res[TradingEvent.UptrendStart] = Constants.BlockingCoefficient;
res[TradingEvent.DowntrendStart] = Constants.UppingCoefficient;
res[TradingEvent.DowntrendEnd] = Constants.LowingCoefficient;
}
return Task.FromResult(res.ToImmutableDictionary());
}
internal static bool IsOperationAllowed(IManagedAccount account, decimal boutPrice, decimal count,
decimal accountCashPartFutures, decimal accountCashPart)
{
if (!BotModeSwitcher.CanPurchase()) return false;
var balance = account.Balance;
var total = account.Total;
var futures = account.Assets.Values.FirstOrDefault(v => v.Type == AssetType.Futures);
if (futures != null)
{
if ((balance - boutPrice * count) / total < accountCashPartFutures) return false;
}
else
{
if ((balance - boutPrice * count) / total < accountCashPart) return false;
}
return true;
}
private static Dictionary<TradingEvent, decimal> GetInitDict(decimal initValue)
{
var values = System.Enum.GetValues<TradingEvent>();
return values.ToDictionary(v => v, v => initValue);
}
private static Dictionary<TradingEvent, decimal> MergeResultsMult(Dictionary<TradingEvent, decimal> res, ImmutableDictionary<TradingEvent, decimal> data)
{
foreach (var k in res.Keys)
{
var valRes = res[k];
var valData = data[k];
res[k] = valRes * valData;
}
return res;
}
private static Dictionary<TradingEvent, decimal> MergeResultsMax(Dictionary<TradingEvent, decimal> res, ImmutableDictionary<TradingEvent, decimal> data)
{
foreach (var k in res.Keys)
{
var valRes = res[k];
var valData = data[k];
res[k] = System.Math.Max(valRes, valData);
}
return res;
}
}
}