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

780 lines
38 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.Common.Enums;
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.Enums;
using KLHZ.Trader.Core.Exchange.Interfaces;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
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.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.Security.Cryptography;
using System.Threading.Channels;
using Tinkoff.InvestApi;
namespace KLHZ.Trader.Core.Exchange.Services
{
public class Trader : IHostedService
{
private readonly IDataBus _dataBus;
private readonly TraderDataProvider _tradeDataProvider;
private readonly PortfolioWrapper _portfolioWrapper;
private readonly ExchangeConfig _exchangeConfig;
private readonly ILogger<Trader> _logger;
private readonly ConcurrentDictionary<string, TradingMode> TradingModes = new();
private readonly ConcurrentDictionary<string, DeferredDeclision> DeferredDeclisions = new();
private readonly ConcurrentDictionary<string, SupportLevel[]> SupportLevels = 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> _usedSupportLevels = new();
private readonly ConcurrentDictionary<string, DateTime> _usedSupportLevelsForClosing = new();
private readonly ConcurrentDictionary<string, ITradeDataItem> _oldItems = new();
private readonly Channel<ITradeDataItem> _pricesChannel = Channel.CreateUnbounded<ITradeDataItem>();
private readonly Channel<ITradeCommand> _commands = Channel.CreateUnbounded<ITradeCommand>();
private readonly Channel<IOrderbook> _orderbooks = Channel.CreateUnbounded<IOrderbook>();
public Trader(
ILogger<Trader> logger,
IOptions<ExchangeConfig> options,
IDataBus dataBus,
PortfolioWrapper portfolioWrapper,
TraderDataProvider tradeDataProvider,
InvestApiClient investApiClient)
{
_portfolioWrapper = portfolioWrapper;
_tradeDataProvider = tradeDataProvider;
_logger = logger;
_dataBus = dataBus;
_exchangeConfig = options.Value;
foreach (var f in _exchangeConfig.TradingInstrumentsFigis)
{
TradingModes[f] = TradingMode.None;
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
_dataBus.AddChannel(nameof(Trader), _pricesChannel);
_dataBus.AddChannel(nameof(Trader), _orderbooks);
_dataBus.AddChannel(nameof(Trader), _commands);
_ = ProcessPrices();
_ = ProcessOrderbooks();
_ = ProcessCommands();
return Task.CompletedTask;
}
private async Task ProcessCommands()
{
while (await _commands.Reader.WaitToReadAsync())
{
var command = await _commands.Reader.ReadAsync();
try
{
if (command.CommandType == TradeCommandType.OpenLong
|| command.CommandType == TradeCommandType.OpenShort)
{
var fakeMessage = new TradeDataItem() { Figi = command.Figi, Ticker = "", Count = command.Count, Direction = 1, IsHistoricalData = false, Time = DateTime.UtcNow, Price = command.RecomendPrice ?? 0m };
var positionType = command.CommandType == TradeCommandType.OpenLong ? PositionType.Long : PositionType.Short;
var st = GetStops(fakeMessage);
var stops = st.GetStops(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 TradeDataItem() { Figi = command.Figi, Ticker = "", Count = command.Count, Direction = 1, IsHistoricalData = false, Time = DateTime.UtcNow, Price = 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)
{
_logger.LogError(ex, "Ошибка при выполнении команды.");
}
}
}
private async Task ProcessOrderbooks()
{
while (await _orderbooks.Reader.WaitToReadAsync())
{
var message = await _orderbooks.Reader.ReadAsync();
await _tradeDataProvider.AddOrderbook(message);
}
}
private async Task ProcessPrices()
{
var pricesCache1 = new Dictionary<string, List<ITradeDataItem>>();
var pricesCache2 = new Dictionary<string, List<ITradeDataItem>>();
while (await _pricesChannel.Reader.WaitToReadAsync())
{
var message = await _pricesChannel.Reader.ReadAsync();
if (!message.IsHistoricalData && DateTime.UtcNow - message.Time > TimeSpan.FromMinutes(1))
{
continue;
}
await CloseMarginPositionsIfNeed(message);
try
{
if (message.IsHistoricalData)
{
message = TraderUtils.FilterHighFreqValues(message, message.Direction == 1 ? pricesCache1 : pricesCache2);
}
if (_exchangeConfig.TradingInstrumentsFigis.Contains(message.Figi) && message.Direction == 1)
{
await _tradeDataProvider.AddData(message);
if (message.Figi == "FUTIMOEXF000")
{
await ProcessIMOEXF(message);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка при боработке новой цены.");
}
}
}
private async Task ProcessIMOEXF(ITradeDataItem message)
{
if (message.Figi == "FUTIMOEXF000")
{
if (DeferredDeclisions.TryGetValue(message.Figi, out var dec))
{
if (dec.ExpirationTime < message.Time)
{
if (dec.Events[TradingEvent.OpenShort] > Constants.BlockingCoefficient)
{
if (dec.Message.Price < message.Price)
{
var stops2 = GetStops(message);
var declisionsStops2 = ProcessStops(stops2, 2m);
var e = TraderUtils.MergeResultsMult(dec.Events, declisionsStops2);
await ExecuteDeclisions(e.ToImmutableDictionary(), message, stops2, 1);
}
}
else if (dec.Events[TradingEvent.OpenLong] > Constants.BlockingCoefficient)
{
if (dec.Message.Price > message.Price)
{
var stops2 = GetStops(message);
var declisionsStops2 = ProcessStops(stops2, 2m);
var e = TraderUtils.MergeResultsMult(dec.Events, declisionsStops2);
await ExecuteDeclisions(e.ToImmutableDictionary(), message, stops2, 1);
}
}
else if (dec.Events[TradingEvent.CloseLong] > Constants.BlockingCoefficient
|| dec.Events[TradingEvent.CloseShort] > Constants.BlockingCoefficient)
{
await ExecuteDeclisions(dec.Events, dec.Message, dec.Stops, 1);
}
DeferredDeclisions.TryRemove(message.Figi, out _);
}
}
await CalcSupportLevels(message, 3, 5);
var stops = GetStops(message);
var pirson = await CalcPirson(message);
var declisionPirson = await ProcessPirson(pirson, message);
var declisionsSupportLevels = await ProcessSupportLevels(message);
var declisionsStops = ProcessStops(stops, 2m);
var res = TraderUtils.MergeResultsMult(declisionPirson, declisionsSupportLevels);
res = TraderUtils.MergeResultsMult(res, declisionsStops);
await ExecuteDeclisions(res.ToImmutableDictionary(), message, stops, 1);
var declision = new DeferredDeclision()
{
Message = message,
Stops = stops,
Events = res.ToImmutableDictionary(),
ExpirationTime = message.Time.AddSeconds(5)
};
if (declision.Events.Values.Any(v => v > Constants.BlockingCoefficient))
{
//DeferredDeclisions.TryAdd(message.Figi, declision);
}
_oldItems[message.Figi] = message;
}
}
private async Task<ImmutableDictionary<TradingEvent, decimal>> ProcessPirson(PirsonCalculatingResult pirson, ITradeDataItem message)
{
var res = TraderUtils.GetInitDict(Constants.BlockingCoefficient);
if (pirson.Success && _pirsonValues.TryGetValue(message.Figi, out var olddpirs))
{
var dpirson = pirson.Pirson - olddpirs;
if (olddpirs < 0 && pirson.Pirson > 0 && pirson.PriceDiff > 0 && (pirson.TradesDiffRelative > 0.2m))
{
res[TradingEvent.OpenLong] = Constants.PowerUppingCoefficient;
}
//if (olddpirs < -0.7m && pirson.Pirson > -0.7m && pirson.PriceDiff > 0 && (pirson.TradesDiffRelative < -0.1m))
//{
// res[TradingEvent.OpenLong] = Constants.PowerUppingCoefficient;
//}
if (olddpirs > 0 && pirson.Pirson < 0 && pirson.PriceDiff < 0 && (pirson.TradesDiffRelative > 0.2m))
{
res[TradingEvent.OpenShort] = Constants.PowerUppingCoefficient;
}
//if (olddpirs > 0.3m && pirson.Pirson < 0.3m && pirson.PriceDiff < 0 && (pirson.TradesDiffRelative > 0.3m))
//{
// res[TradingEvent.OpenShort] = Constants.PowerUppingCoefficient;
//}
if (_dpirsonValues.TryGetValue(message.Figi, out var oldDprison))
{
if (oldDprison > 0.02m && dpirson < -0.02m && pirson.Pirson > 0.7m && pirson.TradesDiffRelative < -0.2m)
{
res[TradingEvent.CloseLong] = Constants.PowerUppingCoefficient;
//await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_out", message.Price);
}
if (oldDprison < 0 && dpirson > 0 && pirson.Pirson < -0.6m && pirson.TradesDiffRelative < -0.1m)
{
res[TradingEvent.CloseShort] = Constants.PowerUppingCoefficient;
// await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_out", message.Price);
}
}
_dpirsonValues[message.Figi] = dpirson;
}
_pirsonValues[message.Figi] = pirson.Pirson;
return res.ToImmutableDictionary();
}
private async Task<PirsonCalculatingResult> CalcPirson(ITradeDataItem message)
{
var cacheSize = TimeSpan.FromSeconds(400);
var smallWindow = TimeSpan.FromSeconds(180);
var bigWindow = TimeSpan.FromSeconds(360);
var meanWindowForCottelation = TimeSpan.FromSeconds(360);
var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
var buys = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, cacheSize, selector: (i) => i.Direction == 1);
var trades = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, cacheSize);
if (trades.TryCalcTimeWindowsDiff(bigWindow, smallWindow, v => v.Count, false, out var tradesDiff, out var tradesDiffRelative)
&& buys.TryCalcTimeDiff(bigWindow, smallWindow, v => v.Price, true, out var pricesDiff))
{
await _tradeDataProvider.LogPrice(message, "privcesDiff", pricesDiff);
await _tradeDataProvider.LogPrice(message, "tradevolume_diff", tradesDiff);
await _tradeDataProvider.AddData(message.Figi, "5min_diff", new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Value2 = tradesDiff,
Value = pricesDiff,
Figi = message.Figi,
Ticker = message.Ticker,
});
var diffs = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, cacheSize, "5min_diff");
if (diffs.TryCalcPirsonCorrelation(meanWindowForCottelation, out var pirson))
{
var res = pirson;
await _tradeDataProvider.LogPrice(message, "diffs_pirson", (decimal)pirson);
//await _tradeDataProvider.AddData(message.Figi, "diffs_pirson", new Contracts.Declisions.Dtos.CachedValue()
//{
// Time = message.Time,
// Value = (decimal)pirson,
// Figi = message.Figi,
// Ticker = message.Ticker,
//});
return new PirsonCalculatingResult()
{
Pirson = res,
PriceDiff = pricesDiff,
TradesDiff = tradesDiff,
TradesDiffRelative = tradesDiffRelative,
Success = true,
};
}
}
return new PirsonCalculatingResult()
{
Success = false,
};
}
private async Task CloseMarginPositionsIfNeed(ITradeDataItem message)
{
var state = ExchangeScheduler.GetCurrentState(message.Time);
if (!message.IsHistoricalData && state == ExchangeState.ClearingTime)
{
var futuresFigis = _portfolioWrapper.Accounts.Values.SelectMany(v => v.Assets.Values.Where(a => a.Type == AssetType.Futures)).ToArray();
await ClosePositions(futuresFigis, message, false);
}
}
private async Task CalcSupportLevels(ITradeDataItem message, int leverage, decimal supportLevelWidth, int depthHours = 3)
{
if (_supportLevelsCalculationTimes.TryGetValue(message.Figi, out var lastTime))
{
if ((message.Time - lastTime).TotalMinutes < 30)
{
return;
}
}
var data = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, TimeSpan.FromHours(depthHours));
if (data.Length > 0)
{
if (data[^1].Time - data[0].Time < TimeSpan.FromHours(0.5))
{
data = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, TimeSpan.FromHours(depthHours + 12));
if (data[^1].Time - data[0].Time < TimeSpan.FromHours(0.5))
{
return;
}
}
var hist = Statistics.CalcHistogram(data);
var convs = Statistics.CalcConvolution(hist, leverage).ToList();
var orderedConvs = convs.OrderByDescending(c => c.Sum).Take(5).ToList();
orderedConvs = [.. orderedConvs.OrderBy(c => c.Value)];
var levelsForAdd = new List<SupportLevel>();
foreach (var c in orderedConvs)
{
var low = c.Value - supportLevelWidth;
var high = c.Value + supportLevelWidth;
if (levelsForAdd.Count > 0)
{
var last = levelsForAdd.Last();
if (last.HighValue < low)
{
levelsForAdd.Add(new SupportLevel()
{
HighValue = high,
LowValue = low,
Value = c.Value,
CalculatedAt = message.Time,
});
}
else if (last.HighValue >= low && last.HighValue < high)
{
levelsForAdd[^1] = new SupportLevel()
{
LowValue = last.LowValue,
HighValue = high,
Value = last.LowValue + (high - last.LowValue) / 2,
CalculatedAt = message.Time,
};
}
}
else
{
levelsForAdd.Add(new SupportLevel()
{
HighValue = high,
LowValue = low,
Value = c.Value,
CalculatedAt = message.Time,
});
}
}
var finalLevels = new SupportLevel[levelsForAdd.Count];
var i = 0;
foreach (var level in levelsForAdd)
{
DateTime? time = null;
foreach (var item in data)
{
if (item.Price >= level.LowValue && item.Price < level.HighValue)
{
time = item.Time;
}
}
finalLevels[i] = new SupportLevel()
{
HighValue = level.HighValue,
LowValue = level.LowValue,
Value = level.Value,
LastLevelTime = time,
CalculatedAt = message.Time,
};
i++;
}
SupportLevels[message.Figi] = finalLevels;
await _tradeDataProvider.LogPrice(message, "support_level_calc", message.Price);
}
_supportLevelsCalculationTimes[message.Figi] = message.Time;
}
private async Task ClosePositions(Asset[] assets, ITradeDataItem message, bool withProfitOnly = true)
{
var loggedDeclisions = 0;
var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi);
var assetsForClose = new List<Asset>();
var price = message.Price;
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)
{
Asset? assetForClose = null;
string? mess = null;
if (withProfitOnly)
{
var profit = 0m;
if (assetType == AssetType.Futures)
{
if (_tradeDataProvider.Orderbooks.TryGetValue(message.Figi, out var orderbook))
{
if (asset.Count < 0 && orderbook.Asks.Length > 0)
{
price = orderbook.Asks[0].Price;
}
else if (orderbook.Bids.Length > 0)
{
price = orderbook.Bids[0].Price;
}
}
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);
assetForClose = asset;
mess = $"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{price}, профит {profit}";
if (loggedDeclisions == 0)
{
loggedDeclisions++;
await _tradeDataProvider.LogDeclision(asset.Count < 0 ? DeclisionTradeAction.CloseShortReal : DeclisionTradeAction.CloseLongReal, message, profit);
}
}
}
else
{
mess = $"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{price}";
assetForClose = asset;
}
if (assetForClose != null && mess != null)
{
await _portfolioWrapper.Accounts[asset.AccountId].ClosePosition(message.Figi);
await _dataBus.Broadcast(new MessageForAdmin() { Text = mess });
}
}
}
private async Task OpenPositions(IManagedAccount[] accounts, ITradeDataItem 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 (TraderUtils.IsOperationAllowed(acc, message.Price, count, _exchangeConfig.AccountCashPartFutures, _exchangeConfig.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.Price, 2)}. Стоп лосс: {(positionType == PositionType.Long ? "-" : "+")}{stopLossShift}. " +
$"Тейк профит: {(positionType == PositionType.Long ? "+" : "-")}{takeProfitShift}"
});
}
if (loggedDeclisions == 0)
{
await _tradeDataProvider.LogDeclision(DeclisionTradeAction.OpenLongReal, message);
loggedDeclisions++;
}
}
}
private async Task ExecuteDeclisions(ImmutableDictionary<TradingEvent, decimal> result, ITradeDataItem message, Stops st, int accountsForOpening = 1)
{
var state = ExchangeScheduler.GetCurrentState(message.Time);
if (result[TradingEvent.OpenLong] >= Constants.UppingCoefficient
&& state == ExchangeState.Open
)
{
var stops = st.GetStops(PositionType.Long);
if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase())
{
var accounts = _portfolioWrapper.Accounts
.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
.Take(accountsForOpening)
.Select(a => a.Value)
.ToArray();
await OpenPositions(accounts, message, PositionType.Long, stops.stopLoss, stops.takeProfit, 1);
}
var val = message.Price;
var valLow = message.Price - stops.stopLoss;
var valHigh = message.Price + stops.takeProfit;
await _tradeDataProvider.LogDeclision(DeclisionTradeAction.OpenLong, val, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message);
//await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsLong, valHigh, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message);
await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsLong, valLow, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message);
}
if (result[TradingEvent.OpenShort] >= Constants.UppingCoefficient
&& state == ExchangeState.Open
)
{
var stops = st.GetStops(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);
}
var val = message.Price;
var valLow = message.Price - stops.takeProfit;
var valHigh = message.Price + stops.stopLoss;
await _tradeDataProvider.LogDeclision(DeclisionTradeAction.OpenShort, val, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message);
//await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsShort, valLow, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message);
await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsShort, valHigh, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message);
}
if (result[TradingEvent.CloseLong] >= 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 _tradeDataProvider.LogDeclision(DeclisionTradeAction.CloseLong, message.Price, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message);
}
if (result[TradingEvent.CloseShort] >= 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 _tradeDataProvider.LogDeclision(DeclisionTradeAction.CloseShort, message.Price, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private decimal GetComission(AssetType assetType)
{
if (assetType == AssetType.Common)
{
return _exchangeConfig.ShareComission;
}
else if (assetType == AssetType.Futures)
{
return _exchangeConfig.FutureComission;
}
else
{
return 0;
}
}
private decimal GetLeverage(string figi, bool isShort)
{
var res = 1m;
var leverage = _exchangeConfig.InstrumentsSettings.FirstOrDefault(l => l.Figi == figi);
if (leverage != null)
{
res = isShort ? leverage.ShortLeverage : leverage.LongLeverage;
}
return res;
}
private Stops GetStops(ITradeDataItem message)
{
var additionalShift = message.Price * 0.001m;
var longStopLossShift = message.Price * 0.0025m;
var longTakeProfitShift = message.Price * 0.02m;
var shortStopLossShift = message.Price * 0.0025m;
var shortTakeProfitShift = message.Price * 0.02m;
if (SupportLevels.TryGetValue(message.Figi, out var levels))
{
if (levels.Length > 0)
{
var levelsByTime = levels.Where(l => l.LastLevelTime.HasValue)
.OrderByDescending(l => l.LastLevelTime)
.ToArray();
if (message.Price >= levelsByTime[0].LowValue && message.Price < levelsByTime[0].HighValue)
{
longStopLossShift = message.Price - levelsByTime[0].LowValue;
shortStopLossShift = levelsByTime[0].HighValue - message.Price;
}
else
{
var levelsByDiffForLong = levels.Where(l => l.LastLevelTime.HasValue)
.OrderBy(l => l.Value - message.Price)
.ToArray();
var levelsByDiffForShort = levels.Where(l => l.LastLevelTime.HasValue)
.OrderByDescending(l => l.Value - message.Price)
.ToArray();
var nearestLevel = levelsByDiffForLong[0];
if (message.Price > nearestLevel.HighValue)
{
longStopLossShift = message.Price - nearestLevel.HighValue + additionalShift;
}
nearestLevel = levelsByDiffForShort[0];
if (message.Price < nearestLevel.LowValue)
{
shortStopLossShift = nearestLevel.LowValue - message.Price + additionalShift;
}
}
}
}
return new Stops(longStopLossShift, longTakeProfitShift, shortStopLossShift, shortTakeProfitShift);
}
private static ImmutableDictionary<TradingEvent, decimal> ProcessStops(Stops stops, decimal meanfullLevel)
{
var res = TraderUtils.GetInitDict(1);
if (stops.LongTakeProfitShift < meanfullLevel || stops.LongStopLossShift < meanfullLevel)
{
res[TradingEvent.OpenLong] = Constants.BlockingCoefficient;
}
if (stops.ShortTakeProfitShift < meanfullLevel || stops.ShortStopLossShift < meanfullLevel)
{
res[TradingEvent.OpenShort] = Constants.BlockingCoefficient;
}
return res.ToImmutableDictionary();
}
private async Task<ImmutableDictionary<TradingEvent, decimal>> ProcessSupportLevels(ITradeDataItem message)
{
var res = TraderUtils.GetInitDict(1);
if (SupportLevels.TryGetValue(message.Figi, out var levels))
{
foreach (var lev in levels)
{
if (message.Price >= lev.LowValue && message.Price < lev.HighValue)
{
await _tradeDataProvider.LogPrice(message, "support_level", message.Price);
}
}
if (levels.Length > 0)
{
var levelsByTime = levels.Where(l => l.LastLevelTime.HasValue)
.OrderByDescending(l => l.LastLevelTime)
.ToArray();
var levelByTime = levelsByTime[0];
if (message.Price >= levelByTime.LowValue && message.Price < levelByTime.HighValue)
{
if (message.Price > levelByTime.Value)
{
res[TradingEvent.OpenLong] = Constants.BlockingCoefficient;
}
if (message.Price < levelByTime.Value)
{
res[TradingEvent.OpenShort] = Constants.BlockingCoefficient;
}
if (_oldItems.TryGetValue(message.Figi, out var old1))
{
var islevelUsed = false;
if (_usedSupportLevelsForClosing.TryGetValue(message.Figi, out var time))
{
if (time == levelByTime.CalculatedAt)
{
islevelUsed = true;
}
}
if (!islevelUsed)
{
if (old1.Price < levelByTime.LowValue || levelByTime.CalculatedAt == message.Time)
{
res[TradingEvent.CloseLong] = Constants.ForceExecuteCoefficient;
_usedSupportLevelsForClosing[message.Figi] = levelByTime.CalculatedAt;
}
if (old1.Price > levelByTime.HighValue || levelByTime.CalculatedAt == message.Time)
{
res[TradingEvent.CloseShort] = Constants.ForceExecuteCoefficient;
_usedSupportLevelsForClosing[message.Figi] = levelByTime.CalculatedAt;
}
}
}
}
else if (_oldItems.TryGetValue(message.Figi, out var old))
{
if (old.Price >= levelByTime.LowValue && old.Price < levelByTime.HighValue)
{
var islevelUsed = false;
if (_usedSupportLevels.TryGetValue(message.Figi, out var time))
{
if (time == levelByTime.CalculatedAt)
{
islevelUsed = true;
}
}
if (!islevelUsed)
{
if (message.Price < levelByTime.LowValue)
{
res[TradingEvent.OpenShort] = Constants.ForceExecuteCoefficient;
_usedSupportLevels[message.Figi] = levelByTime.CalculatedAt;
}
else if (message.Price > levelByTime.HighValue)
{
res[TradingEvent.OpenLong] = Constants.ForceExecuteCoefficient;
_usedSupportLevels[message.Figi] = levelByTime.CalculatedAt;
}
}
}
}
}
}
return res.ToImmutableDictionary();
}
}
}