IsBuyAllowed

main
vlad zverzhkhovskiy 2025-09-05 17:02:31 +03:00
parent 81d772fea9
commit f95a34e5a1
8 changed files with 160 additions and 34 deletions

View File

@ -21,7 +21,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
internal static TradingEvent CheckUptrendStart(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{
var periodStat = GetTwoPeriodsProcessingData(times, prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
var isStartOk = periodStat.Success && periodStat.DiffStart < 0.5m * meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff;
return isStartOk && isEndOk && prices[periodStat.Start] - prices[periodStat.End] >= meanfullDiff ? TradingEvent.UptrendStart : TradingEvent.None;
}

View File

@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />

View File

@ -0,0 +1,107 @@
using Castle.Core.Logging;
using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Common.Messaging.Services;
using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using KLHZ.Trader.Core.Exchange.Models.Configs;
using KLHZ.Trader.Core.Math.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NSubstitute;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KLHZ.Trader.Core.Tests
{
public class TraderTests
{
[Test]
public void IsBuyAllowedTest1()
{
var account = new ManagedAccount("111");
account.Total = 10000;
account.Balance = 9000;
account.Assets["123"] = new Asset()
{
AccountId = account.AccountId,
Figi = "123",
Ticker = "123",
Type = AssetType.Futures,
};
Assert.IsTrue(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 3000, 1, 0.5m, 0.3m));
}
[Test]
public void IsBuyAllowedTest2()
{
var account = new ManagedAccount("111");
account.Total = 10000;
account.Balance = 5000;
account.Assets["123"] = new Asset()
{
AccountId = account.AccountId,
Figi = "123",
Ticker = "123",
Type = AssetType.Futures,
};
Assert.IsFalse(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 3000, 1, 0.5m, 0.3m));
}
[Test]
public void IsBuyAllowedTest3()
{
var account = new ManagedAccount("111");
account.Total = 10000;
account.Balance = 5000;
account.Assets["123"] = new Asset()
{
AccountId = account.AccountId,
Figi = "123",
Ticker = "123",
Type = AssetType.Futures,
};
Assert.IsFalse(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 1500, 2, 0.5m, 0.3m));
}
[Test]
public void IsBuyAllowedTest4()
{
var account = new ManagedAccount("111");
account.Total = 10000;
account.Balance = 3000;
account.Assets["123"] = new Asset()
{
AccountId = account.AccountId,
Figi = "123",
Ticker = "123",
Type = AssetType.Futures,
};
Assert.IsFalse(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 1500, 1, 0.5m, 0.3m));
}
[Test]
public void IsBuyAllowedTest5()
{
var account = new ManagedAccount("111");
account.Total = 10000;
account.Balance = 5000;
account.Assets["123"] = new Asset()
{
AccountId = account.AccountId,
Figi = "123",
Ticker = "123",
Type = AssetType.Common,
};
Assert.IsTrue(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 3000, 1, 0.5m, 0.1m));
}
}
}

View File

@ -1,42 +1,42 @@
namespace KLHZ.Trader.Core.Common
{
public class BotModeSwitcher
public static class BotModeSwitcher
{
private readonly object _locker = new();
private bool _canSell = true;
private bool _canPurchase = true;
private readonly static object _locker = new();
private static bool _canSell = true;
private static bool _canPurchase = true;
public bool CanSell()
public static bool CanSell()
{
lock (_locker)
return _canSell;
}
public bool CanPurchase()
public static bool CanPurchase()
{
lock (_locker)
return _canPurchase;
}
public void StopSelling()
public static void StopSelling()
{
lock (_locker)
_canSell = false;
}
public void StopPurchase()
public static void StopPurchase()
{
lock (_locker)
_canPurchase = false;
}
public void StartSelling()
public static void StartSelling()
{
lock (_locker)
_canSell = true;
}
public void StartPurchase()
public static void StartPurchase()
{
lock (_locker)
_canPurchase = true;

View File

@ -28,12 +28,11 @@ namespace KLHZ.Trader.Core.Exchange.Services
public class Trader : IHostedService
{
private readonly IDataBus _dataBus;
private readonly BotModeSwitcher _botModeSwitcher;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly TradeDataProvider _tradeDataProvider;
private readonly ILogger<Trader> _logger;
private readonly ConcurrentDictionary<string, DeferredTrade> DeferredLongOpens = new();
private readonly ConcurrentDictionary<string, DeferredTrade> DeferredLongCloses = new();
internal readonly ConcurrentDictionary<string, DeferredTrade> DeferredLongOpens = new();
internal readonly ConcurrentDictionary<string, DeferredTrade> DeferredLongCloses = new();
private readonly ConcurrentDictionary<string, DateTime> OpeningStops = new();
private readonly ConcurrentDictionary<string, InstrumentSettings> Leverages = new();
private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new();
@ -44,7 +43,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly decimal _shareComission;
private readonly decimal _accountCashPart;
private readonly decimal _accountCashPartFutures;
private readonly decimal _defaultBuyPartOfAccount;
private readonly string[] _tradingInstrumentsFigis = [];
private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
@ -52,8 +50,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly CancellationTokenSource _cts = new();
public Trader(
ILogger<Trader> logger,
BotModeSwitcher botModeSwitcher,
IServiceProvider provider,
IOptions<ExchangeConfig> options,
IDataBus dataBus,
IDbContextFactory<TraderDbContext> dbContextFactory,
@ -62,14 +58,12 @@ namespace KLHZ.Trader.Core.Exchange.Services
{
_tradeDataProvider = tradeDataProvider;
_logger = logger;
_botModeSwitcher = botModeSwitcher;
_dataBus = dataBus;
_dbContextFactory = dbContextFactory;
_futureComission = options.Value.FutureComission;
_shareComission = options.Value.ShareComission;
_accountCashPart = options.Value.AccountCashPart;
_accountCashPartFutures = options.Value.AccountCashPartFutures;
_defaultBuyPartOfAccount = options.Value.DefaultBuyPartOfAccount;
_tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis;
_buyStopLength = (double)options.Value.StopBuyLengthMinuts;
@ -133,7 +127,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
.ToArray();
foreach (var acc in accounts)
{
if (IsBuyAllowed(acc.Value, message.Value, 1))
if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart))
{
await _dataBus.Broadcast(new TradeCommand()
{
@ -211,6 +205,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
var state = ExchangeScheduler.GetCurrentState(message.Time);
if (state == ExchangeState.ClearingTime
&& !message.IsHistoricalData
&& data.timestamps.Length > 1
&& (data.timestamps[data.timestamps.Length - 1] - data.timestamps[data.timestamps.Length - 2]) > TimeSpan.FromMinutes(3))
{
@ -236,7 +231,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 45, 180, 2.5m);
var resultLongClose = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 15, 120, 2.5m).events;
var uptrendStarts = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(15), 2m, 10);
var uptrendStarts = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(20), 1.5m, 15);
//var uptrendStarts2 = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), 1.5m, 2);
@ -252,7 +247,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
}
if ((resultLongClose & TradingEvent.StopBuy) == TradingEvent.StopBuy)
{
var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(_buyStopLength);
var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(_buyStopLength/2);
OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
//LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuy, message);
}
@ -405,9 +400,10 @@ namespace KLHZ.Trader.Core.Exchange.Services
return res;
}
private bool IsBuyAllowed(ManagedAccount account, decimal boutPrice, decimal count)
internal static bool IsBuyAllowed(ManagedAccount account, decimal boutPrice, decimal count,
decimal accountCashPartFutures, decimal accountCashPart)
{
if (!_botModeSwitcher.CanPurchase()) return false;
if (!BotModeSwitcher.CanPurchase()) return false;
var balance = account.Balance;
var total = account.Total;
@ -415,11 +411,11 @@ namespace KLHZ.Trader.Core.Exchange.Services
var futures = account.Assets.Values.FirstOrDefault(v => v.Type == AssetType.Futures);
if (futures != null)
{
if ((balance - boutPrice * count) / total < _accountCashPartFutures) return false;
if ((balance - boutPrice * count) / total < accountCashPartFutures) return false;
}
else
{
if ((balance - boutPrice * count) / total < _accountCashPart) return false;
if ((balance - boutPrice * count) / total < accountCashPart) return false;
}
return true;

View File

@ -0,0 +1,25 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using KLHZ.Trader.Core.Exchange.Services;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KLHZ.Trader.Core.Exchange.Utils
{
internal static class TraderFuncs
{
//internal static TradeCommand[] CreateBuyCommands(ConcurrentDictionary<string, ManagedAccount> accounts, INewPrice message, List<Declision> declisions)
//{
// var accs = accounts.Where(a => !a.Value.Assets.ContainsKey(message.Figi))
// .ToArray();
//}
}
}

View File

@ -15,13 +15,11 @@ namespace KLHZ.Trader.Core.TG.Services
public class BotMessagesHandler : IUpdateHandler
{
private readonly ImmutableArray<long> _admins = [];
private readonly BotModeSwitcher _botModeSwitcher;
private readonly IDataBus _eventBus;
private readonly ILogger<BotMessagesHandler> _logger;
public BotMessagesHandler(BotModeSwitcher botModeSwitcher, IDataBus eventBus, IOptions<TgBotConfig> options, ILogger<BotMessagesHandler> logger)
public BotMessagesHandler(IDataBus eventBus, IOptions<TgBotConfig> options, ILogger<BotMessagesHandler> logger)
{
_logger = logger;
_botModeSwitcher = botModeSwitcher;
_eventBus = eventBus;
_admins = ImmutableArray.CreateRange(options.Value.Admins);
}
@ -52,25 +50,25 @@ namespace KLHZ.Trader.Core.TG.Services
}
case Constants.BotCommandsButtons.EnableSelling:
{
_botModeSwitcher.StartSelling();
BotModeSwitcher.StartSelling();
await botClient.SendMessage(update.Message.Chat, "Продажи начаты!");
break;
}
case Constants.BotCommandsButtons.DisableSelling:
{
_botModeSwitcher.StopSelling();
BotModeSwitcher.StopSelling();
await botClient.SendMessage(update.Message.Chat, "Продажи остановлены!");
break;
}
case Constants.BotCommandsButtons.EnablePurchases:
{
_botModeSwitcher.StartPurchase();
BotModeSwitcher.StartPurchase();
await botClient.SendMessage(update.Message.Chat, "Покупки начаты!");
break;
}
case Constants.BotCommandsButtons.DisablePurchases:
{
_botModeSwitcher.StopPurchase();
BotModeSwitcher.StopPurchase();
await botClient.SendMessage(update.Message.Chat, "Покупки остановлены!");
break;
}

View File

@ -53,7 +53,6 @@ builder.Services.AddHostedService<TradingCommandsExecutor>();
builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>();
builder.Services.AddSingleton<TradeDataProvider>();
builder.Services.AddSingleton<BotModeSwitcher>();
builder.Services.AddSingleton<IDataBus, DataBus>();
for (int i = 0; i < 10; i++)