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

780 lines
40 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.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;
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 ExchangeConfig _exchangeConfig;
private readonly ILogger<Trader> _logger;
private readonly ConcurrentDictionary<string, TradingMode> TradingModes = new();
private readonly ConcurrentDictionary<string, decimal> DPirsonValues = 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 Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
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 == 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 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<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();
if (!message.IsHistoricalData && DateTime.UtcNow - message.Time > TimeSpan.FromMinutes(1))
{
continue;
}
var changeMods = TraderUtils.GetInitDict(0);
try
{
message = TraderUtils.FilterHighFreqValues(message, message.Direction == 1 ? pricesCache1 : pricesCache2);
#region Добавление данных в кеши.
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,
Count = message.Count,
Price = message.Value,
});
await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._5minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = (decimal)message.Count,
Price = message.Value,
});
await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, Constants._15minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = (decimal)message.Count,
Price = message.Value,
});
await _tradeDataProvider.AddDataTo1MinuteWindowCache(message.Figi, Constants._1minBuyCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = (decimal)message.Count,
Price = message.Value,
});
}
if (message.Direction == 2)
{
await _tradeDataProvider.AddDataTo5MinuteWindowCache(message.Figi, Constants._5minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = (decimal)message.Count,
Price = message.Value,
});
await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, Constants._15minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = (decimal)message.Count,
Price = message.Value,
});
await _tradeDataProvider.AddDataTo1MinuteWindowCache(message.Figi, Constants._1minSellCacheKey, new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = (decimal)message.Count,
Price = message.Value,
});
await _tradeDataProvider.AddDataTo20SecondsWindowCache(message.Figi, "2", new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = message.Count,
Price = message.Value,
});
}
}
#endregion
if (_exchangeConfig.TradingInstrumentsFigis.Contains(message.Figi) && (message.Figi == "FUTIMOEXF000" || message.Figi == "BBG004730N88") && message.Direction == 1)
{
var smallWindow = TimeSpan.FromSeconds(120);
var bigWindow = TimeSpan.FromSeconds(240);
var meanWindow = TimeSpan.FromSeconds(240);
var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
try
{
var buys = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, Constants._15minBuyCacheKey);
var sells = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, Constants._15minSellCacheKey);
var trades = buys.ToList();
trades.AddRange(sells);
var trades2 = trades.OrderBy(t => t.Time).ToArray();
if (trades2.TryCalcTimeWindowsDiff(bigWindow, smallWindow, v => v.Count, false, out var tradesDiff)
&& buys.TryCalcTimeWindowsDiff(bigWindow, smallWindow, v => v.Price, true, out var pricesDiff))
{
await LogPrice(message, "privcesDiff", pricesDiff);
await LogPrice(message, "tradevolume_diff", tradesDiff);
await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, "5min_diff", new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = tradesDiff,
Price = pricesDiff,
});
var diffs = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, "5min_diff");
if (diffs.TryCalcTimeWindowsDiff(bigWindow, smallWindow, (c) => c.Price, true, out var resdp)
&& diffs.TryCalcTimeWindowsDiff(bigWindow, smallWindow, (c) => c.Count, true, out var resv))
{
await LogPrice(message, "privcesDiffDiff", (decimal)resdp);
await LogPrice(message, "tradevolume_diff_diff", (decimal)resv);
await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, "5min_diff_diff", new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = resv,
Price = resdp,
});
if (diffs.TryCalcPirsonCorrelation(meanWindow, out var pirson))
{
await LogPrice(message, "diffs_pirson", (decimal)pirson);
await _tradeDataProvider.AddDataTo15MinuteWindowCache(message.Figi, "diffs_pirson", new Contracts.Declisions.Dtos.CachedValue()
{
Time = message.Time,
Count = (decimal)pirson,
});
var diffs_pirson = await _tradeDataProvider.GetDataFrom15MinuteWindowCache(message.Figi, "diffs_pirson");
if (diffs_pirson.TryCalcTimeWindowsDiff(bigWindow, smallWindow, (c) => c.Count, true, out var res))
{
await LogPrice(message, "diffs_pirson_diff", (decimal)res);
if (DPirsonValues.TryGetValue(message.Figi, out var olddpirs))
{
if (olddpirs < 0 && res > 0)
{
await LogPrice(message, "diffs_pirson_diff_point0", message.Value);
}
if (olddpirs < 0.25m && res > 0.25m)
{
await LogPrice(message, "diffs_pirson_diff_point0.25", message.Value);
}
if (olddpirs < 0.5m && res > 0.5m)
{
await LogPrice(message, "diffs_pirson_diff_point0.5", message.Value);
}
}
DPirsonValues[message.Figi] = res;
}
}
}
}
if (timesCache.TryGetValue(message.Figi, out var dt))
{
if ((message.Time - dt).TotalSeconds > 10)
{
timesCache[message.Figi] = message.Time;
var newMod = await CalcTradingMode2(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 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)
{
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);
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);
}
}
for (int i = 0; i < assetsForClose.Count; i++)
{
var asset = assetsForClose[i];
await _portfolioWrapper.Accounts[asset.AccountId].ClosePosition(message.Figi);
await _dataBus.Broadcast(new MessageForAdmin() { Text = messages[i] });
}
}
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 (TraderUtils.IsOperationAllowed(acc, message.Value, 1, _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.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 ExecuteDeclisions(INewPrice message, ImmutableDictionary<TradingEvent, decimal> result)
{
var state = ExchangeScheduler.GetCurrentState();
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 * 10)
{
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 * 10)
{
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 LogPrice(string figi, string ticker, DateTime time, decimal value, string processor)
{
await _tradeDataProvider.LogPrice(new ProcessedPrice()
{
Figi = figi,
Ticker = ticker,
Processor = processor,
Time = 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 _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 async ValueTask<TradingMode> CalcTradingMode2(INewPrice message)
{
var res = TradingMode.None;
var data = await _tradeDataProvider.GetData(message.Figi, TimeSpan.FromMinutes(20));
if (data.isFullIntervalExists)
{
if (LocalTrends.TryCalcTrendDiff(data.timestamps, data.prices, out var diff))
{
if (diff >= -2 && diff <= 2)
{
res = TradingMode.Stable;
}
else if (diff < -6)
{
res = TradingMode.Dropping;
}
else if (diff > 6)
{
res = TradingMode.Growing;
}
else if (diff < -2)
{
res = TradingMode.SlowDropping;
}
else if (diff > 2)
{
res = TradingMode.SlowGrowing;
}
}
}
return res;
}
private (decimal stopLoss, decimal takeProfit) GetStops(INewPrice message, PositionType type)
{
var mode = TradingModes[message.Figi];
decimal stopLossShift = 2m;
decimal takeProfitShift = 6;
if (mode == TradingMode.Growing && type == PositionType.Long)
{
takeProfitShift = 15;
//stopLossShift = 2;
}
if (mode == TradingMode.Growing && type == PositionType.Short)
{
//stopLossShift = 2;
takeProfitShift = 3m;
}
if (mode == TradingMode.Stable && type == PositionType.Long)
{
takeProfitShift = 3m;
}
if (mode == TradingMode.Stable && type == PositionType.Short)
{
takeProfitShift = 3m;
//stopLossShift = 10;
}
if (mode == TradingMode.SlowDropping && type == PositionType.Short)
{
}
if (mode == TradingMode.SlowDropping && type == PositionType.Long)
{
takeProfitShift = 1.5m;
//stopLossShift = 10;
}
if (mode == TradingMode.SlowGrowing && type == PositionType.Short)
{
//takeProfitShift = 2.5m;
//stopLossShift = 10;
}
if (mode == TradingMode.SlowGrowing && type == PositionType.Long)
{
}
if (mode == TradingMode.Dropping && type == PositionType.Short)
{
takeProfitShift = 15;
//stopLossShift = 2;
}
if (mode == TradingMode.Dropping && type == PositionType.Long)
{
//stopLossShift = 2;
takeProfitShift = 3m;
}
return (stopLossShift, takeProfitShift);
}
private Task<ImmutableDictionary<TradingEvent, decimal>> GetTradingModeMods(INewPrice message)
{
var res = TraderUtils.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());
}
}
}