1209 lines
60 KiB
C#
1209 lines
60 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|