Postgres (#30)

* Add postgres

* Migrate users

* Migrate geneticRequest

* Try to fix Concurrent call

* Fix asyncawait

* Fix async and concurrent

* Migrate backtests

* Add cache for user by address

* Fix backtest migration

* Fix not open connection

* Fix backtest command error

* Fix concurrent

* Fix all concurrency

* Migrate TradingRepo

* Fix scenarios

* Migrate statistic repo

* Save botbackup

* Add settings et moneymanagement

* Add bot postgres

* fix a bit more backups

* Fix bot model

* Fix loading backup

* Remove cache market for read positions

* Add workers to postgre

* Fix workers api

* Reduce get Accounts for workers

* Migrate synth to postgre

* Fix backtest saved

* Remove mongodb

* botservice decorrelation

* Fix tradingbot scope call

* fix tradingbot

* fix concurrent

* Fix scope for genetics

* Fix account over requesting

* Fix bundle backtest worker

* fix a lot of things

* fix tab backtest

* Remove optimized moneymanagement

* Add light signal to not use User and too much property

* Make money management lighter

* insert indicators to awaitable

* Migrate add strategies to await

* Refactor scenario and indicator retrieval to use asynchronous methods throughout the application

* add more async await

* Add services

* Fix and clean

* Fix bot a bit

* Fix bot and add message for cooldown

* Remove fees

* Add script to deploy db

* Update dfeeploy script

* fix script

* Add idempotent script and backup

* finish script migration

* Fix did user and agent name on start bot
This commit is contained in:
Oda
2025-07-27 15:42:17 +02:00
committed by GitHub
parent 361bfbf6e8
commit 422fecea7b
294 changed files with 23953 additions and 7272 deletions

View File

@@ -12,13 +12,13 @@ namespace Managing.Application.Abstractions
/// </summary>
/// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance</returns>
ITradingBot CreateTradingBot(TradingBotConfig config);
Task<ITradingBot> CreateTradingBot(TradingBotConfig config);
/// <summary>
/// Creates a trading bot for backtesting using the unified TradingBot class
/// </summary>
/// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance configured for backtesting</returns>
ITradingBot CreateBacktestTradingBot(TradingBotConfig config);
Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config);
}
}

View File

@@ -7,38 +7,38 @@ namespace Managing.Application.Abstractions;
public interface IBotService
{
void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data);
Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data);
void AddSimpleBotToCache(IBot bot);
void AddTradingBotToCache(ITradingBot bot);
List<ITradingBot> GetActiveBots();
IEnumerable<BotBackup> GetSavedBots();
void StartBotFromBackup(BotBackup backupBot);
BotBackup GetBotBackup(string identifier);
Task<IEnumerable<BotBackup>> GetSavedBotsAsync();
Task StartBotFromBackup(BotBackup backupBot);
Task<BotBackup> GetBotBackup(string identifier);
/// <summary>
/// Creates a trading bot using the unified TradingBot class
/// </summary>
/// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance</returns>
ITradingBot CreateTradingBot(TradingBotConfig config);
Task<ITradingBot> CreateTradingBot(TradingBotConfig config);
/// <summary>
/// Creates a trading bot for backtesting using the unified TradingBot class
/// </summary>
/// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance configured for backtesting</returns>
ITradingBot CreateBacktestTradingBot(TradingBotConfig config);
Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config);
// Legacy methods - these will use TradingBot internally but maintain backward compatibility
ITradingBot CreateScalpingBot(TradingBotConfig config);
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
ITradingBot CreateFlippingBot(TradingBotConfig config);
ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
Task<ITradingBot> CreateScalpingBot(TradingBotConfig config);
Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config);
Task<ITradingBot> CreateFlippingBot(TradingBotConfig config);
Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config);
IBot CreateSimpleBot(string botName, Workflow workflow);
Task<string> StopBot(string botName);
Task<bool> DeleteBot(string botName);
Task<string> RestartBot(string botName);
void ToggleIsForWatchingOnly(string botName);
Task ToggleIsForWatchingOnly(string botName);
Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig);
}

View File

@@ -1,15 +0,0 @@
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions
{
public interface IMoneyManagementService
{
Task<MoneyManagement> CreateOrUpdateMoneyManagement(User user, MoneyManagement request);
Task<MoneyManagement> GetMoneyMangement(User user, string name);
Task<MoneyManagement> GetMoneyMangement(string name);
IEnumerable<MoneyManagement> GetMoneyMangements(User user);
bool DeleteMoneyManagement(User user, string name);
bool DeleteMoneyManagements(User user);
}
}

View File

@@ -7,14 +7,10 @@ namespace Managing.Application.Abstractions
{
public interface IScenarioService
{
IEnumerable<Scenario> GetScenarios();
Scenario CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1);
IEnumerable<Indicator> GetIndicators();
bool DeleteStrategy(string name);
bool DeleteScenario(string name);
Scenario GetScenario(string name);
Task<Scenario> CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1);
Task<IEnumerable<Indicator>> GetIndicatorsAsync();
Indicator CreateStrategy(IndicatorType type,
Task<Indicator> CreateStrategy(IndicatorType type,
string name,
int? period = null,
int? fastPeriods = null,
@@ -25,21 +21,19 @@ namespace Managing.Application.Abstractions
int? smoothPeriods = null,
int? cyclePeriods = null);
bool DeleteStrategies();
bool DeleteScenarios();
bool UpdateScenario(string name, List<string> strategies, int? loopbackPeriod);
Task<bool> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod);
bool UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods,
Task<bool> UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods,
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods);
IEnumerable<Scenario> GetScenariosByUser(User user);
Scenario CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1);
IEnumerable<Indicator> GetIndicatorsByUser(User user);
bool DeleteIndicatorByUser(User user, string name);
bool DeleteScenarioByUser(User user, string name);
Scenario GetScenarioByUser(User user, string name);
Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user);
Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1);
Task<IEnumerable<Indicator>> GetIndicatorsByUserAsync(User user);
Task<bool> DeleteIndicatorByUser(User user, string name);
Task<bool> DeleteScenarioByUser(User user, string name);
Task<Scenario> GetScenarioByUser(User user, string name);
Indicator CreateIndicatorForUser(User user,
Task<Indicator> CreateIndicatorForUser(User user,
IndicatorType type,
string name,
int? period = null,
@@ -51,11 +45,11 @@ namespace Managing.Application.Abstractions
int? smoothPeriods = null,
int? cyclePeriods = null);
bool DeleteStrategiesByUser(User user);
bool DeleteScenariosByUser(User user);
bool UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod);
Task<bool> DeleteStrategiesByUser(User user);
Task<bool> DeleteScenariosByUser(User user);
Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod);
bool UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, int? fastPeriods,
Task<bool> UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, int? fastPeriods,
int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods,
int? cyclePeriods);
}

View File

@@ -1,8 +1,10 @@
namespace Managing.Application.Abstractions;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions;
public interface ISettingsService
{
bool SetupSettings();
Task<bool> SetupSettings();
Task<bool> ResetSettings();
Task<bool> CreateDefaultConfiguration(Domain.Users.User user);
Task<bool> CreateDefaultConfiguration(User user);
}

View File

@@ -3,7 +3,6 @@ using Managing.Domain.Accounts;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
@@ -16,7 +15,7 @@ namespace Managing.Application.Abstractions
Account Account { get; set; }
FixedSizeQueue<Candle> OptimizedCandles { get; set; }
HashSet<Candle> Candles { get; set; }
HashSet<Signal> Signals { get; set; }
HashSet<LightSignal> Signals { get; set; }
List<Position> Positions { get; set; }
Dictionary<DateTime, decimal> WalletBalances { get; set; }
Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
@@ -24,7 +23,7 @@ namespace Managing.Application.Abstractions
DateTime CreateDate { get; }
DateTime PreloadSince { get; set; }
int PreloadedCandlesCount { get; set; }
decimal Fee { get; set; }
Task Run();
Task ToggleIsForWatchOnly();
@@ -36,7 +35,7 @@ namespace Managing.Application.Abstractions
Task LoadAccount();
Task<Position> OpenPositionManually(TradeDirection direction);
Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
bool tradeClosingPosition = false);
/// <summary>

View File

@@ -100,9 +100,18 @@ public class AccountService : IAccountService
public async Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance)
{
var account = await _accountRepository.GetAccountByNameAsync(name);
if (account == null)
{
throw new ArgumentException($"Account '{name}' not found");
}
ManageProperties(hideSecrets, getBalance, account);
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
if (account.User == null && account.User != null)
{
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
}
return account;
}
@@ -123,9 +132,31 @@ public class AccountService : IAccountService
return account;
}
public IEnumerable<Account> GetAccounts(bool hideSecrets, bool getBalance)
public async Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true,
bool getBalance = false)
{
var result = _accountRepository.GetAccounts();
var account = await _accountRepository.GetAccountByNameAsync(accountName);
if (account != null)
{
ManageProperties(hideSecrets, getBalance, account);
if (account.User != null)
{
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
}
}
return account;
}
public async Task<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance)
{
return await GetAccountsAsync(hideSecrets, getBalance);
}
public async Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance)
{
var result = await _accountRepository.GetAccountsAsync();
var accounts = new List<Account>();
foreach (var account in result)
@@ -139,15 +170,21 @@ public class AccountService : IAccountService
public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true)
{
var cacheKey = $"user-account-{user.Name}";
return _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, hideSecrets, false); },
TimeSpan.FromMinutes(5));
return GetAccountsByUserAsync(user, hideSecrets).Result;
}
private IEnumerable<Account> GetAccounts(User user, bool hideSecrets, bool getBalance)
public async Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true)
{
var result = _accountRepository.GetAccounts();
var cacheKey = $"user-account-{user.Name}";
// For now, we'll get fresh data since caching async operations requires more complex logic
// This can be optimized later with proper async caching
return await GetAccountsAsync(user, hideSecrets, false);
}
private async Task<IEnumerable<Account>> GetAccountsAsync(User user, bool hideSecrets, bool getBalance)
{
var result = await _accountRepository.GetAccountsAsync();
var accounts = new List<Account>();
foreach (var account in result.Where(a => a.User.Name == user.Name))
@@ -161,11 +198,16 @@ public class AccountService : IAccountService
public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets)
{
var cacheKey = $"user-account-balance-{user.Name}";
var accounts = _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, true, true); },
TimeSpan.FromHours(3));
return GetAccountsBalancesByUserAsync(user, hideSecrets).Result;
}
return accounts;
public async Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets)
{
var cacheKey = $"user-account-balance-{user.Name}";
// For now, get fresh data since caching async operations requires more complex logic
// This can be optimized later with proper async caching
return await GetAccountsAsync(user, hideSecrets, true);
}
public async Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName)
@@ -200,7 +242,8 @@ public class AccountService : IAccountService
}
}
public async Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
public async Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
{
// Get the account for the user
var account = await GetAccountByUser(user, accountName, true, false);
@@ -220,12 +263,12 @@ public class AccountService : IAccountService
{
// Call the Web3ProxyService to swap GMX tokens
var swapInfos = await _web3ProxyService.SwapGmxTokensAsync(
account.Key,
fromTicker,
toTicker,
amount,
orderType,
triggerRatio,
account.Key,
fromTicker,
toTicker,
amount,
orderType,
triggerRatio,
allowedSlippage
);
@@ -239,7 +282,8 @@ public class AccountService : IAccountService
}
}
public async Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null)
public async Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker,
decimal amount, int? chainId = null)
{
// Get the account for the user
var account = await GetAccountByUser(user, accountName, true, false);
@@ -271,10 +315,10 @@ public class AccountService : IAccountService
{
// Call the Web3ProxyService to send tokens
var swapInfos = await _web3ProxyService.SendTokenAsync(
account.Key,
recipientAddress,
ticker,
amount,
account.Key,
recipientAddress,
ticker,
amount,
chainId
);

View File

@@ -121,6 +121,12 @@ namespace Managing.Application.Backtesting
result.StartDate = startDate;
result.EndDate = endDate;
// Ensure RequestId is set - required for PostgreSQL NOT NULL constraint
if (string.IsNullOrEmpty(result.RequestId))
{
result.RequestId = Guid.NewGuid().ToString();
}
if (save && user != null)
{
_backtestRepository.InsertBacktestForUser(user, result);
@@ -138,8 +144,9 @@ namespace Managing.Application.Backtesting
var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user);
if (refundSuccess)
{
_logger.LogInformation(
"Successfully refunded credits for user {UserName} after backtest failure", user.Name);
_logger.LogError(
"Successfully refunded credits for user {UserName} after backtest failure: {message}",
user.Name, ex.Message);
}
else
{
@@ -188,7 +195,7 @@ namespace Managing.Application.Backtesting
string requestId = null,
object metadata = null)
{
var tradingBot = _botFactory.CreateBacktestTradingBot(config);
var tradingBot = await _botFactory.CreateBacktestTradingBot(config);
// Scenario and indicators should already be loaded in constructor by BotService
// This is just a validation check to ensure everything loaded properly
@@ -215,26 +222,7 @@ namespace Managing.Application.Backtesting
private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
{
var accounts = _accountService.GetAccounts(false, false).ToArray();
var account = accounts.FirstOrDefault(a =>
a.Name.Equals(config.AccountName, StringComparison.OrdinalIgnoreCase) &&
a.Exchange == TradingExchanges.GmxV2);
if (account == null && accounts.Any())
{
account = accounts.First();
}
if (account != null)
{
return account;
}
return new Account
{
Name = config.AccountName,
Exchange = TradingExchanges.GmxV2
};
return await _accountService.GetAccountByAccountName(config.AccountName, false, false);
}
private List<Candle> GetCandles(Ticker ticker, Timeframe timeframe,
@@ -270,13 +258,13 @@ namespace Managing.Application.Backtesting
_logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}",
totalCandles, config.Ticker, config.Timeframe);
bot.WalletBalances.Add(candles.FirstOrDefault().Date, config.BotTradingBalance);
bot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance);
foreach (var candle in candles)
{
bot.OptimizedCandles.Enqueue(candle);
bot.Candles.Add(candle);
bot.Run();
await bot.Run();
currentCandle++;
@@ -318,8 +306,6 @@ namespace Managing.Application.Backtesting
var finalPnl = bot.GetProfitAndLoss();
var winRate = bot.GetWinRate();
var optimizedMoneyManagement =
TradingBox.GetBestMoneyManagement(candles, bot.Positions, config.MoneyManagement);
var stats = TradingHelpers.GetStatistics(bot.WalletBalances);
var growthPercentage =
TradingHelpers.GetGrowthFromInitalBalance(bot.WalletBalances.FirstOrDefault().Value, finalPnl);
@@ -357,7 +343,6 @@ namespace Managing.Application.Backtesting
Fees = fees,
WalletBalances = bot.WalletBalances.ToList(),
Statistics = stats,
OptimizedMoneyManagement = optimizedMoneyManagement,
IndicatorsValues = withCandles
? AggregateValues(indicatorsValues, bot.IndicatorsValues)
: new Dictionary<IndicatorType, IndicatorsResultBase>(),
@@ -442,11 +427,11 @@ namespace Managing.Application.Backtesting
return indicatorsValues;
}
public bool DeleteBacktest(string id)
public async Task<bool> DeleteBacktestAsync(string id)
{
try
{
_backtestRepository.DeleteBacktestByIdForUser(null, id);
await _backtestRepository.DeleteBacktestByIdForUserAsync(null, id);
return true;
}
catch (Exception ex)
@@ -476,12 +461,24 @@ namespace Managing.Application.Backtesting
return backtests;
}
public async Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user)
{
var backtests = await _backtestRepository.GetBacktestsByUserAsync(user);
return backtests;
}
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
{
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
return backtests;
}
public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId)
{
var backtests = await _backtestRepository.GetBacktestsByRequestIdAsync(requestId);
return backtests;
}
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
@@ -490,9 +487,19 @@ namespace Managing.Application.Backtesting
return (backtests, totalCount);
}
public Backtest GetBacktestByIdForUser(User user, string id)
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);
var (backtests, totalCount) =
await _backtestRepository.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy,
sortOrder);
return (backtests, totalCount);
}
public async Task<Backtest> GetBacktestByIdForUserAsync(User user, string id)
{
var backtest = await _backtestRepository.GetBacktestByIdForUserAsync(user, id);
if (backtest == null)
return null;
@@ -504,12 +511,12 @@ namespace Managing.Application.Backtesting
var account = new Account
{ Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };
var candles = _exchangeService.GetCandlesInflux(
var candles = await _exchangeService.GetCandlesInflux(
account.Exchange,
backtest.Config.Ticker,
backtest.StartDate,
backtest.Config.Timeframe,
backtest.EndDate).Result;
backtest.EndDate);
if (candles != null && candles.Count > 0)
{
@@ -525,11 +532,11 @@ namespace Managing.Application.Backtesting
return backtest;
}
public bool DeleteBacktestByUser(User user, string id)
public async Task<bool> DeleteBacktestByUserAsync(User user, string id)
{
try
{
_backtestRepository.DeleteBacktestByIdForUser(user, id);
await _backtestRepository.DeleteBacktestByIdForUserAsync(user, id);
return true;
}
catch (Exception ex)
@@ -539,11 +546,11 @@ namespace Managing.Application.Backtesting
}
}
public bool DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids)
public async Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids)
{
try
{
_backtestRepository.DeleteBacktestsByIdsForUser(user, ids);
await _backtestRepository.DeleteBacktestsByIdsForUserAsync(user, ids);
return true;
}
catch (Exception ex)
@@ -567,11 +574,11 @@ namespace Managing.Application.Backtesting
}
}
public bool DeleteBacktestsByRequestId(string requestId)
public async Task<bool> DeleteBacktestsByRequestIdAsync(string requestId)
{
try
{
_backtestRepository.DeleteBacktestsByRequestId(requestId);
await _backtestRepository.DeleteBacktestsByRequestIdAsync(requestId);
return true;
}
catch (Exception ex)
@@ -589,6 +596,14 @@ namespace Managing.Application.Backtesting
return (backtests, totalCount);
}
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(
User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var (backtests, totalCount) =
await _backtestRepository.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder);
return (backtests, totalCount);
}
// Bundle backtest methods
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
{
@@ -600,27 +615,53 @@ namespace Managing.Application.Backtesting
return _backtestRepository.GetBundleBacktestRequestsByUser(user);
}
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user)
{
return await _backtestRepository.GetBundleBacktestRequestsByUserAsync(user);
}
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
{
return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
}
public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id)
{
return await _backtestRepository.GetBundleBacktestRequestByIdForUserAsync(user, id);
}
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
{
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
}
public async Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest)
{
await _backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest);
}
public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
{
_backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
}
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id)
{
await _backtestRepository.DeleteBundleBacktestRequestByIdForUserAsync(user, id);
}
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
{
// Use the repository method to get all bundles, then filter by status
return _backtestRepository.GetBundleBacktestRequestsByStatus(status);
}
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(
BundleBacktestRequestStatus status)
{
return await _backtestRepository.GetBundleBacktestRequestsByStatusAsync(status);
}
/// <summary>
/// Sends a LightBacktestResponse to all SignalR subscribers of a bundle request.
/// </summary>

View File

@@ -1,5 +1,6 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.ManageBot;
using Managing.Domain.Bots;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
@@ -14,6 +15,7 @@ namespace Managing.Application.Bots.Base
private readonly ILogger<TradingBot> _tradingBotLogger;
private readonly ITradingService _tradingService;
private readonly IBotService _botService;
private readonly IBackupBotService _backupBotService;
public BotFactory(
IExchangeService exchangeService,
@@ -21,7 +23,8 @@ namespace Managing.Application.Bots.Base
IMessengerService messengerService,
IAccountService accountService,
ITradingService tradingService,
IBotService botService)
IBotService botService,
IBackupBotService backupBotService)
{
_tradingBotLogger = tradingBotLogger;
_exchangeService = exchangeService;
@@ -29,23 +32,24 @@ namespace Managing.Application.Bots.Base
_accountService = accountService;
_tradingService = tradingService;
_botService = botService;
_backupBotService = backupBotService;
}
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
{
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService);
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService, _backupBotService);
}
ITradingBot IBotFactory.CreateTradingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
{
// Delegate to BotService which handles scenario loading properly
return _botService.CreateTradingBot(config);
return await _botService.CreateTradingBot(config);
}
ITradingBot IBotFactory.CreateBacktestTradingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
{
// Delegate to BotService which handles scenario loading properly
return _botService.CreateBacktestTradingBot(config);
return await _botService.CreateBacktestTradingBot(config);
}
}
}

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions;
using Managing.Application.ManageBot;
using Managing.Domain.Bots;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
@@ -10,13 +11,16 @@ namespace Managing.Application.Bots
{
public readonly ILogger<TradingBot> Logger;
private readonly IBotService _botService;
private readonly IBackupBotService _backupBotService;
private Workflow _workflow;
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow, IBotService botService) :
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow, IBotService botService,
IBackupBotService backupBotService) :
base(name)
{
Logger = logger;
_botService = botService;
_backupBotService = backupBotService;
_workflow = workflow;
Interval = 100;
}
@@ -35,20 +39,20 @@ namespace Managing.Application.Bots
Logger.LogInformation(Identifier);
Logger.LogInformation(DateTime.Now.ToString());
await _workflow.Execute();
SaveBackup();
await SaveBackup();
Logger.LogInformation("__________________________________________________");
});
}
public override void SaveBackup()
public override async Task SaveBackup()
{
var data = JsonConvert.SerializeObject(_workflow);
_botService.SaveOrUpdateBotBackup(User, Identifier, Status, data);
await _backupBotService.SaveOrUpdateBotBackup(User, Identifier, Status, new TradingBotBackup());
}
public override void LoadBackup(BotBackup backup)
{
_workflow = JsonConvert.DeserializeObject<Workflow>(backup.Data);
_workflow = new Workflow();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,14 @@ using System.Text.Json;
using GeneticSharp;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Core;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Risk;
using Managing.Domain.Scenarios;
using Managing.Domain.Users;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -22,6 +24,7 @@ public class GeneticService : IGeneticService
private readonly IBacktester _backtester;
private readonly ILogger<GeneticService> _logger;
private readonly IMessengerService _messengerService;
private readonly IServiceScopeFactory _serviceScopeFactory;
// Predefined parameter ranges for each indicator (matching backtestGenetic.tsx)
public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new()
@@ -188,12 +191,14 @@ public class GeneticService : IGeneticService
IGeneticRepository geneticRepository,
IBacktester backtester,
ILogger<GeneticService> logger,
IMessengerService messengerService)
IMessengerService messengerService,
IServiceScopeFactory serviceScopeFactory)
{
_geneticRepository = geneticRepository;
_backtester = backtester;
_logger = logger;
_messengerService = messengerService;
_serviceScopeFactory = serviceScopeFactory;
}
public GeneticRequest CreateGeneticRequest(
@@ -247,9 +252,9 @@ public class GeneticService : IGeneticService
return _geneticRepository.GetGeneticRequestByIdForUser(user, id);
}
public void UpdateGeneticRequest(GeneticRequest geneticRequest)
public async Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest)
{
_geneticRepository.UpdateGeneticRequest(geneticRequest);
await _geneticRepository.UpdateGeneticRequestAsync(geneticRequest);
}
public void DeleteGeneticRequestByIdForUser(User user, string id)
@@ -257,9 +262,9 @@ public class GeneticService : IGeneticService
_geneticRepository.DeleteGeneticRequestByIdForUser(user, id);
}
public IEnumerable<GeneticRequest> GetPendingGeneticRequests()
public Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status)
{
return _geneticRepository.GetPendingGeneticRequests();
return _geneticRepository.GetGeneticRequestsAsync(status);
}
/// <summary>
@@ -277,7 +282,7 @@ public class GeneticService : IGeneticService
// Update status to running
request.Status = GeneticRequestStatus.Running;
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
// Create or resume chromosome for trading bot configuration
TradingBotChromosome chromosome;
@@ -307,7 +312,7 @@ public class GeneticService : IGeneticService
}
// Create fitness function first
var fitness = new TradingBotFitness(_backtester, request, _logger);
var fitness = new TradingBotFitness(_serviceScopeFactory, request, _logger);
// Create genetic algorithm with better configuration
var ga = new GeneticAlgorithm(
@@ -341,7 +346,7 @@ public class GeneticService : IGeneticService
// Run the genetic algorithm with periodic checks for cancellation
var generationCount = 0;
ga.GenerationRan += (sender, e) =>
ga.GenerationRan += async (sender, e) =>
{
generationCount = ga.GenerationsNumber;
@@ -362,7 +367,7 @@ public class GeneticService : IGeneticService
request.BestChromosome = JsonSerializer.Serialize(geneValues);
}
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
// Check for cancellation
if (cancellationToken.IsCancellationRequested)
@@ -381,7 +386,7 @@ public class GeneticService : IGeneticService
// Update request status to pending so it can be resumed
request.Status = GeneticRequestStatus.Pending;
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
return new GeneticAlgorithmResult
{
@@ -413,7 +418,7 @@ public class GeneticService : IGeneticService
completed_at = DateTime.UtcNow
});
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
// Send notification about the completed genetic algorithm
try
@@ -442,7 +447,7 @@ public class GeneticService : IGeneticService
request.Status = GeneticRequestStatus.Failed;
request.ErrorMessage = ex.Message;
request.CompletedAt = DateTime.UtcNow;
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
throw;
}
@@ -505,7 +510,7 @@ public class TradingBotChromosome : ChromosomeBase
private readonly List<IndicatorType> _eligibleIndicators;
private readonly double _maxTakeProfit;
private readonly Random _random = new Random();
private int[]? _indicatorSelectionPattern;
private int[] _indicatorSelectionPattern;
// Gene structure:
// 0-3: Trading parameters (takeProfit, stopLoss, cooldownPeriod, maxLossStreak)
@@ -552,7 +557,7 @@ public class TradingBotChromosome : ChromosomeBase
GenerateIndicatorSelectionPattern();
}
return new Gene(_indicatorSelectionPattern![geneIndex - 5]);
return new Gene(_indicatorSelectionPattern[geneIndex - 5]);
}
else
{
@@ -790,7 +795,7 @@ public class TradingBotChromosome : ChromosomeBase
return _random.Next((int)range.min, (int)range.max + 1);
}
private string? GetParameterName(int index)
private string GetParameterName(int index)
{
return index switch
{
@@ -879,14 +884,14 @@ public class GeneticIndicator
/// </summary>
public class TradingBotFitness : IFitness
{
private readonly IBacktester _backtester;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly GeneticRequest _request;
private GeneticAlgorithm _geneticAlgorithm;
private readonly ILogger<GeneticService> _logger;
public TradingBotFitness(IBacktester backtester, GeneticRequest request, ILogger<GeneticService> logger)
public TradingBotFitness(IServiceScopeFactory serviceScopeFactory, GeneticRequest request, ILogger<GeneticService> logger)
{
_backtester = backtester;
_serviceScopeFactory = serviceScopeFactory;
_request = request;
_logger = logger;
}
@@ -909,19 +914,22 @@ public class TradingBotFitness : IFitness
// Get current generation number (default to 0 if not available)
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
// Run backtest
var backtest = _backtester.RunTradingBotBacktest(
config,
_request.StartDate,
_request.EndDate,
_request.User,
true,
false, // Don't include candles
_request.RequestId,
new
{
generation = currentGeneration
}
// Run backtest using scoped service to avoid DbContext concurrency issues
var backtest = ServiceScopeHelpers.WithScopedService<IBacktester, Backtest>(
_serviceScopeFactory,
backtester => backtester.RunTradingBotBacktest(
config,
_request.StartDate,
_request.EndDate,
_request.User,
true,
false, // Don't include candles
_request.RequestId,
new
{
generation = currentGeneration
}
)
).Result;
// Calculate multi-objective fitness based on backtest results
@@ -929,8 +937,9 @@ public class TradingBotFitness : IFitness
return fitness;
}
catch (Exception)
catch (Exception ex)
{
_logger.LogWarning("Fitness evaluation failed for chromosome: {Message}", ex.Message);
// Return low fitness for failed backtests
return 0.1;
}

View File

@@ -0,0 +1,52 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Bots;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
{
public interface IBackupBotService
{
Task<BotBackup> GetBotBackup(string identifier);
Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data);
}
public class BackupBotService : IBackupBotService
{
private readonly IBotRepository _botRepository;
public BackupBotService(IBotRepository botRepository)
{
_botRepository = botRepository;
}
public async Task<BotBackup> GetBotBackup(string identifier)
{
return await _botRepository.GetBotByIdentifierAsync(identifier);
}
public async Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data)
{
var backup = await GetBotBackup(identifier);
if (backup != null)
{
backup.LastStatus = status;
backup.Data = data;
await _botRepository.UpdateBackupBot(backup);
}
else
{
var botBackup = new BotBackup
{
LastStatus = status,
User = user,
Identifier = identifier,
Data = data
};
await _botRepository.InsertBotAsync(botBackup);
}
}
}
}

View File

@@ -6,8 +6,8 @@ using Managing.Application.Bots;
using Managing.Domain.Bots;
using Managing.Domain.Users;
using Managing.Domain.Workflows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
@@ -22,13 +22,16 @@ namespace Managing.Application.ManageBot
private readonly ITradingService _tradingService;
private readonly IMoneyManagementService _moneyManagementService;
private readonly IUserService _userService;
private readonly IBackupBotService _backupBotService;
private readonly IServiceScopeFactory _scopeFactory;
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
new ConcurrentDictionary<string, BotTaskWrapper>();
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService)
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService,
IBackupBotService backupBotService, IServiceScopeFactory scopeFactory)
{
_botRepository = botRepository;
_exchangeService = exchangeService;
@@ -38,35 +41,8 @@ namespace Managing.Application.ManageBot
_tradingService = tradingService;
_moneyManagementService = moneyManagementService;
_userService = userService;
}
public BotBackup GetBotBackup(string identifier)
{
return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier);
}
public void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data)
{
var backup = GetBotBackup(identifier);
if (backup != null)
{
backup.LastStatus = status;
backup.Data = data;
_botRepository.UpdateBackupBot(backup);
}
else
{
var botBackup = new BotBackup
{
LastStatus = status,
User = user,
Identifier = identifier,
Data = data
};
_botRepository.InsertBotAsync(botBackup);
}
_backupBotService = backupBotService;
_scopeFactory = scopeFactory;
}
public class BotTaskWrapper
@@ -96,6 +72,27 @@ namespace Managing.Application.ManageBot
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
}
private async Task InitBot(ITradingBot bot, BotBackup backupBot)
{
var user = await _userService.GetUser(backupBot.User.Name);
bot.User = user;
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
bot.LoadBackup(backupBot);
// Only start the bot if the backup status is Up
if (backupBot.LastStatus == BotStatus.Up)
{
// Start the bot asynchronously without waiting for completion
_ = Task.Run(() => bot.Start());
}
else
{
// Keep the bot in Down status if it was originally Down
bot.Stop();
}
}
public List<ITradingBot> GetActiveBots()
{
var bots = _botTasks.Values
@@ -107,17 +104,17 @@ namespace Managing.Application.ManageBot
return bots;
}
public IEnumerable<BotBackup> GetSavedBots()
public async Task<IEnumerable<BotBackup>> GetSavedBotsAsync()
{
return _botRepository.GetBots();
return await _botRepository.GetBotsAsync();
}
public void StartBotFromBackup(BotBackup backupBot)
public async Task StartBotFromBackup(BotBackup backupBot)
{
object bot = null;
Task botTask = null;
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
var scalpingBotData = backupBot.Data;
// Get the config directly from the backup
var scalpingConfig = scalpingBotData.Config;
@@ -137,7 +134,7 @@ namespace Managing.Application.ManageBot
// Ensure the scenario is properly loaded from database if needed
if (scalpingConfig.Scenario == null && !string.IsNullOrEmpty(scalpingConfig.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(scalpingConfig.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(scalpingConfig.ScenarioName);
if (scenario != null)
{
scalpingConfig.Scenario = scenario;
@@ -158,7 +155,7 @@ namespace Managing.Application.ManageBot
// Ensure critical properties are set correctly for restored bots
scalpingConfig.IsForBacktest = false;
bot = CreateTradingBot(scalpingConfig);
bot = await CreateTradingBot(scalpingConfig);
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
if (bot != null && botTask != null)
@@ -168,29 +165,38 @@ namespace Managing.Application.ManageBot
}
}
private void InitBot(ITradingBot bot, BotBackup backupBot)
public async Task<BotBackup> GetBotBackup(string identifier)
{
var user = _userService.GetUser(backupBot.User.Name);
bot.User = user;
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
bot.LoadBackup(backupBot);
return await _botRepository.GetBotByIdentifierAsync(identifier);
}
// Only start the bot if the backup status is Up
if (backupBot.LastStatus == BotStatus.Up)
public async Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data)
{
var backup = await GetBotBackup(identifier);
if (backup != null)
{
// Start the bot asynchronously without waiting for completion
_ = Task.Run(() => bot.Start());
backup.LastStatus = status;
backup.Data = data;
await _botRepository.UpdateBackupBot(backup);
}
else
{
// Keep the bot in Down status if it was originally Down
bot.Stop();
var botBackup = new BotBackup
{
LastStatus = status,
User = user,
Identifier = identifier,
Data = data
};
await _botRepository.InsertBotAsync(botBackup);
}
}
public IBot CreateSimpleBot(string botName, Workflow workflow)
{
return new SimpleBot(botName, _tradingBotLogger, workflow, this);
return new SimpleBot(botName, _tradingBotLogger, workflow, this, _backupBotService);
}
public async Task<string> StopBot(string identifier)
@@ -263,7 +269,7 @@ namespace Managing.Application.ManageBot
// Restart the bot (this will update StartupTime)
bot.Restart();
// Start the bot asynchronously without waiting for completion
_ = Task.Run(() => bot.Start());
@@ -282,12 +288,12 @@ namespace Managing.Application.ManageBot
return BotStatus.Down.ToString();
}
public void ToggleIsForWatchingOnly(string identifier)
public async Task ToggleIsForWatchingOnly(string identifier)
{
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
botTaskWrapper.BotInstance is ITradingBot tradingBot)
{
tradingBot.ToggleIsForWatchOnly().Wait();
await tradingBot.ToggleIsForWatchOnly();
}
}
@@ -305,7 +311,7 @@ namespace Managing.Application.ManageBot
// Ensure the scenario is properly loaded from database if needed
if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(newConfig.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(newConfig.ScenarioName);
if (scenario != null)
{
newConfig.Scenario = scenario;
@@ -365,12 +371,12 @@ namespace Managing.Application.ManageBot
}
public ITradingBot CreateTradingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -386,22 +392,15 @@ namespace Managing.Application.ManageBot
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
}
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateBacktestTradingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -418,22 +417,15 @@ namespace Managing.Application.ManageBot
}
config.IsForBacktest = true;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateScalpingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateScalpingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -450,22 +442,15 @@ namespace Managing.Application.ManageBot
}
config.FlipPosition = false;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -483,22 +468,15 @@ namespace Managing.Application.ManageBot
config.IsForBacktest = true;
config.FlipPosition = false;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateFlippingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateFlippingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -515,22 +493,15 @@ namespace Managing.Application.ManageBot
}
config.FlipPosition = true;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -548,14 +519,7 @@ namespace Managing.Application.ManageBot
config.IsForBacktest = true;
config.FlipPosition = true;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Managing.Application.ManageBot
_accountService = accountService;
}
public async Task<Dictionary<User, List<ITradingBot>>> Handle(GetAllAgentsCommand request,
public Task<Dictionary<User, List<ITradingBot>>> Handle(GetAllAgentsCommand request,
CancellationToken cancellationToken)
{
var result = new Dictionary<User, List<ITradingBot>>();
@@ -55,7 +55,7 @@ namespace Managing.Application.ManageBot
result[bot.User].Add(bot);
}
return result;
return Task.FromResult(result);
}
/// <summary>

View File

@@ -18,9 +18,9 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
_botService = botService;
}
public Task<string> Handle(LoadBackupBotCommand request, CancellationToken cancellationToken)
public async Task<string> Handle(LoadBackupBotCommand request, CancellationToken cancellationToken)
{
var backupBots = _botService.GetSavedBots().ToList();
var backupBots = (await _botService.GetSavedBotsAsync()).ToList();
_logger.LogInformation("Loading {Count} backup bots.", backupBots.Count);
var result = new Dictionary<string, BotStatus>();
@@ -42,7 +42,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
_botService.StartBotFromBackup(backupBot);
// Wait a short time to allow the bot to initialize
Thread.Sleep(1000);
await Task.Delay(1000, cancellationToken);
// Try to get the active bot multiple times to ensure it's properly started
int attempts = 0;
@@ -74,7 +74,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
attempts++;
if (attempts < maxAttempts)
{
Thread.Sleep(1000); // Wait another second before next attempt
await Task.Delay(1000, cancellationToken); // Wait another second before next attempt
}
}
@@ -114,7 +114,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
_logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus);
return Task.FromResult(finalStatus.ToString());
return finalStatus.ToString();
}
}

View File

@@ -81,11 +81,11 @@ namespace Managing.Application.ManageBot
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
};
var tradingBot = _botFactory.CreateTradingBot(configToUse);
var tradingBot = await _botFactory.CreateTradingBot(configToUse);
tradingBot.User = request.User;
// Log the configuration being used
await LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created");
LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created");
_botService.AddTradingBotToCache(tradingBot);
return tradingBot.GetStatus();
@@ -98,7 +98,7 @@ namespace Managing.Application.ManageBot
/// </summary>
/// <param name="bot">The trading bot instance</param>
/// <param name="context">Context information for the log</param>
private async Task LogBotConfigurationAsync(ITradingBot bot, string context)
private void LogBotConfigurationAsync(ITradingBot bot, string context)
{
try
{

View File

@@ -1,35 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<Compile Remove="MoneyManagements\Abstractions\**" />
<EmbeddedResource Remove="MoneyManagements\Abstractions\**" />
<None Remove="MoneyManagements\Abstractions\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="MoneyManagements\Abstractions\**"/>
<EmbeddedResource Remove="MoneyManagements\Abstractions\**"/>
<None Remove="MoneyManagements\Abstractions\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.1" />
<PackageReference Include="GeneticSharp" Version="3.1.4" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Polly" Version="8.4.0" />
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.1"/>
<PackageReference Include="GeneticSharp" Version="3.1.4"/>
<PackageReference Include="MediatR" Version="12.2.0"/>
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0"/>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1"/>
<PackageReference Include="Polly" Version="8.4.0"/>
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj" />
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj"/>
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj"/>
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj"/>
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj"/>
</ItemGroup>
</Project>

View File

@@ -38,9 +38,28 @@ public class MoneyManagementService : IMoneyManagementService
if (moneyManagement == null)
{
request.User = user;
await _settingsRepository.InsertMoneyManagement(request);
return request;
// Convert MoneyManagement to LightMoneyManagement for insertion
var lightRequest = new LightMoneyManagement
{
Name = request.Name,
Timeframe = request.Timeframe,
StopLoss = request.StopLoss,
TakeProfit = request.TakeProfit,
Leverage = request.Leverage
};
await _settingsRepository.InsertMoneyManagement(lightRequest, user);
// Return the created money management with user
return new MoneyManagement
{
Name = lightRequest.Name,
Timeframe = lightRequest.Timeframe,
StopLoss = lightRequest.StopLoss,
TakeProfit = lightRequest.TakeProfit,
Leverage = lightRequest.Leverage,
User = user
};
}
else
{
@@ -51,14 +70,28 @@ public class MoneyManagementService : IMoneyManagementService
"You do not have permission to update this money management strategy.");
}
moneyManagement.StopLoss = request.StopLoss;
moneyManagement.TakeProfit = request.TakeProfit;
moneyManagement.Leverage = request.Leverage;
moneyManagement.Timeframe = request.Timeframe;
moneyManagement.User = user;
// Convert to LightMoneyManagement for update
var lightRequest = new LightMoneyManagement
{
Name = request.Name,
Timeframe = request.Timeframe,
StopLoss = request.StopLoss,
TakeProfit = request.TakeProfit,
Leverage = request.Leverage
};
_settingsRepository.UpdateMoneyManagement(moneyManagement);
return moneyManagement;
await _settingsRepository.UpdateMoneyManagementAsync(lightRequest, user);
// Return updated money management
return new MoneyManagement
{
Name = lightRequest.Name,
Timeframe = lightRequest.Timeframe,
StopLoss = lightRequest.StopLoss,
TakeProfit = lightRequest.TakeProfit,
Leverage = lightRequest.Leverage,
User = user
};
}
}
@@ -67,18 +100,18 @@ public class MoneyManagementService : IMoneyManagementService
return await _settingsRepository.GetMoneyManagement(name);
}
public IEnumerable<MoneyManagement> GetMoneyMangements(User user)
public async Task<IEnumerable<MoneyManagement>> GetMoneyMangements(User user)
{
try
{
// Try to use user-specific repository method first
return _settingsRepository.GetMoneyManagementsByUser(user);
return await _settingsRepository.GetMoneyManagementsByUserAsync(user);
}
catch
{
// Fall back to filtering if user-specific endpoint is not implemented
return _settingsRepository.GetMoneyManagements()
.Where(mm => mm.User?.Name == user.Name);
var allMoneyManagements = await _settingsRepository.GetMoneyManagementsAsync();
return allMoneyManagements.Where(mm => mm.User?.Name == user.Name);
}
}
@@ -106,19 +139,19 @@ public class MoneyManagementService : IMoneyManagementService
return moneyManagement;
}
public bool DeleteMoneyManagement(User user, string name)
public async Task<bool> DeleteMoneyManagement(User user, string name)
{
try
{
try
{
// Try to use user-specific repository method first
_settingsRepository.DeleteMoneyManagementByUser(user, name);
await _settingsRepository.DeleteMoneyManagementByUserAsync(user, name);
}
catch
{
// Fall back to verifying user ownership before deletion
var moneyManagement = _settingsRepository.GetMoneyManagement(name).Result;
var moneyManagement = await _settingsRepository.GetMoneyManagement(name);
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
{
@@ -126,7 +159,7 @@ public class MoneyManagementService : IMoneyManagementService
"You do not have permission to delete this money management strategy.");
}
_settingsRepository.DeleteMoneyManagement(name);
await _settingsRepository.DeleteMoneyManagementAsync(name);
}
return true;
@@ -138,14 +171,14 @@ public class MoneyManagementService : IMoneyManagementService
}
}
public bool DeleteMoneyManagements(User user)
public async Task<bool> DeleteMoneyManagements(User user)
{
try
{
try
{
// Try to use user-specific repository method first
_settingsRepository.DeleteMoneyManagementsByUser(user);
await _settingsRepository.DeleteMoneyManagementsByUserAsync(user);
}
catch
{

View File

@@ -4,7 +4,6 @@ using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Users;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using static Managing.Common.Enums;
namespace Managing.Application.Scenarios
@@ -20,20 +19,20 @@ namespace Managing.Application.Scenarios
_tradingService = tradingService;
}
public Scenario CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1)
public async Task<Scenario> CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1)
{
var scenario = new Scenario(name, loopbackPeriod);
foreach (var strategy in strategies)
{
scenario.AddIndicator(_tradingService.GetStrategyByName(strategy));
scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy));
}
try
{
_tradingService.InsertScenario(scenario);
await _tradingService.InsertScenarioAsync(scenario);
}
catch (MongoCommandException ex)
catch (Exception ex)
{
_logger.LogError(ex.Message);
throw new Exception("Cannot create scenario");
@@ -42,7 +41,7 @@ namespace Managing.Application.Scenarios
return scenario;
}
public Indicator CreateStrategy(
public async Task<Indicator> CreateStrategy(
IndicatorType type,
string name,
int? period = null,
@@ -65,30 +64,25 @@ namespace Managing.Application.Scenarios
stochPeriods,
smoothPeriods,
cyclePeriods);
_tradingService.InsertStrategy(strategy);
await _tradingService.InsertStrategyAsync(strategy);
return strategy;
}
public IEnumerable<Scenario> GetScenarios()
public async Task<IEnumerable<Scenario>> GetScenariosAsync()
{
return _tradingService.GetScenarios();
return await _tradingService.GetScenariosAsync();
}
public Scenario GetScenario(string name)
public async Task<IEnumerable<Indicator>> GetIndicatorsAsync()
{
return _tradingService.GetScenarioByName(name);
return await _tradingService.GetStrategiesAsync();
}
public IEnumerable<Indicator> GetIndicators()
{
return _tradingService.GetStrategies();
}
public bool DeleteScenario(string name)
public async Task<bool> DeleteScenarioAsync(string name)
{
try
{
_tradingService.DeleteScenario(name);
await _tradingService.DeleteScenarioAsync(name);
return true;
}
catch (Exception ex)
@@ -98,61 +92,19 @@ namespace Managing.Application.Scenarios
}
}
public bool DeleteStrategy(string name)
public async Task<bool> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod)
{
try
{
_tradingService.DeleteStrategy(name);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteStrategies()
{
try
{
_tradingService.DeleteStrategies();
}
catch (Exception)
{
return false;
}
return true;
}
public bool DeleteScenarios()
{
try
{
_tradingService.DeleteScenarios();
}
catch (Exception)
{
return false;
}
return true;
}
public bool UpdateScenario(string name, List<string> strategies, int? loopbackPeriod)
{
try
{
var scenario = _tradingService.GetScenarioByName(name);
var scenario = await _tradingService.GetScenarioByNameAsync(name);
scenario.Indicators.Clear();
foreach (var strategy in strategies)
{
scenario.AddIndicator(_tradingService.GetStrategyByName(strategy));
scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy));
}
scenario.LoopbackPeriod = loopbackPeriod ?? 1;
_tradingService.UpdateScenario(scenario);
await _tradingService.UpdateScenarioAsync(scenario);
return true;
}
catch (Exception e)
@@ -162,13 +114,13 @@ namespace Managing.Application.Scenarios
}
}
public bool UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods,
public async Task<bool> UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods,
int? slowPeriods,
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
{
try
{
var strategy = _tradingService.GetStrategyByName(name);
var strategy = await _tradingService.GetStrategyByNameAsync(name);
strategy.Type = indicatorType;
strategy.Period = period;
strategy.FastPeriods = fastPeriods;
@@ -178,7 +130,7 @@ namespace Managing.Application.Scenarios
strategy.StochPeriods = stochPeriods;
strategy.SmoothPeriods = smoothPeriods;
strategy.CyclePeriods = cyclePeriods;
_tradingService.UpdateStrategy(strategy);
await _tradingService.UpdateStrategyAsync(strategy);
return true;
}
catch (Exception e)
@@ -188,15 +140,13 @@ namespace Managing.Application.Scenarios
}
}
// User-specific methods implementation
public IEnumerable<Scenario> GetScenariosByUser(User user)
public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user)
{
var scenarios = _tradingService.GetScenarios();
var scenarios = await _tradingService.GetScenariosAsync();
return scenarios.Where(s => s.User?.Name == user.Name);
}
public Scenario CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1)
public async Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1)
{
var scenario = new Scenario(name, loopbackPeriod ?? 1)
{
@@ -205,82 +155,104 @@ namespace Managing.Application.Scenarios
foreach (var strategyName in strategies)
{
var strategy = _tradingService.GetStrategyByName(strategyName);
var strategy = await _tradingService.GetStrategyByNameAsync(strategyName);
if (strategy != null && strategy.User?.Name == user.Name)
{
scenario.AddIndicator(strategy);
}
}
_tradingService.InsertScenario(scenario);
await _tradingService.InsertScenarioAsync(scenario);
return scenario;
}
public IEnumerable<Indicator> GetIndicatorsByUser(User user)
public async Task<IEnumerable<Indicator>> GetIndicatorsByUserAsync(User user)
{
var strategies = _tradingService.GetStrategies();
return strategies.Where(s => s.User?.Name == user.Name);
var indicators = await GetIndicatorsAsync();
return indicators.Where(s => s.User?.Name == user.Name);
}
public bool DeleteIndicatorByUser(User user, string name)
public async Task<bool> DeleteIndicatorByUser(User user, string name)
{
var strategy = _tradingService.GetStrategyByName(name);
var strategy = await _tradingService.GetStrategyByNameAsync(name);
if (strategy != null && strategy.User?.Name == user.Name)
{
_tradingService.DeleteStrategy(strategy.Name);
await _tradingService.DeleteStrategyAsync(strategy.Name);
return true;
}
return false;
}
public bool DeleteScenarioByUser(User user, string name)
public async Task<bool> DeleteScenarioByUser(User user, string name)
{
var scenario = _tradingService.GetScenarioByName(name);
if (scenario != null && scenario.User?.Name == user.Name)
try
{
_tradingService.DeleteScenario(scenario.Name);
var scenarios = await GetScenariosByUserAsync(user);
foreach (var scenario in scenarios.Where(s => s.Name == name))
{
await _tradingService.DeleteScenarioAsync(scenario.Name);
}
return true;
}
return false;
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public Scenario GetScenarioByUser(User user, string name)
public async Task<bool> DeleteScenariosByUser(User user)
{
var scenario = _tradingService.GetScenarioByName(name);
try
{
var scenarios = await GetScenariosByUserAsync(user);
foreach (var scenario in scenarios)
{
await _tradingService.DeleteScenarioAsync(scenario.Name);
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public async Task<Scenario> GetScenarioByUser(User user, string name)
{
var scenario = await _tradingService.GetScenarioByNameAsync(name);
return scenario != null && scenario.User?.Name == user.Name ? scenario : null;
}
public Indicator CreateIndicatorForUser(User user, IndicatorType type, string name, int? period = null,
public async Task<Indicator> CreateIndicatorForUser(User user, IndicatorType type, string name, int? period = null,
int? fastPeriods = null, int? slowPeriods = null, int? signalPeriods = null,
double? multiplier = null, int? stochPeriods = null, int? smoothPeriods = null,
int? cyclePeriods = null)
{
// Create a new strategy using the existing implementation
var strategy = CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods,
var strategy = await CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods,
multiplier, stochPeriods, smoothPeriods, cyclePeriods);
// Set the user
strategy.User = user;
// Update the strategy to save the user property
_tradingService.UpdateStrategy(strategy);
await _tradingService.UpdateStrategyAsync(strategy);
return strategy;
}
public bool DeleteStrategiesByUser(User user)
public async Task<bool> DeleteStrategiesByUser(User user)
{
try
{
var strategies = GetIndicatorsByUser(user);
var strategies = await GetIndicatorsByUserAsync(user);
foreach (var strategy in strategies)
{
_tradingService.DeleteStrategy(strategy.Name);
await _tradingService.DeleteStrategyAsync(strategy.Name);
}
return true;
}
catch (Exception ex)
@@ -290,29 +262,9 @@ namespace Managing.Application.Scenarios
}
}
public bool DeleteScenariosByUser(User user)
public async Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod)
{
try
{
var scenarios = GetScenariosByUser(user);
foreach (var scenario in scenarios)
{
_tradingService.DeleteScenario(scenario.Name);
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod)
{
var scenario = _tradingService.GetScenarioByName(name);
var scenario = await _tradingService.GetScenarioByNameAsync(name);
if (scenario == null || scenario.User?.Name != user.Name)
{
return false;
@@ -323,29 +275,29 @@ namespace Managing.Application.Scenarios
foreach (var strategyName in strategies)
{
var strategy = _tradingService.GetStrategyByName(strategyName);
var strategy = await _tradingService.GetStrategyByNameAsync(strategyName);
if (strategy != null && strategy.User?.Name == user.Name)
{
scenario.AddIndicator(strategy);
}
}
_tradingService.UpdateScenario(scenario);
await _tradingService.UpdateScenarioAsync(scenario);
return true;
}
public bool UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period,
public async Task<bool> UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period,
int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier,
int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
{
var strategy = _tradingService.GetStrategyByName(name);
var strategy = await _tradingService.GetStrategyByNameAsync(name);
if (strategy == null || strategy.User?.Name != user.Name)
{
return false;
}
// Use the existing update strategy logic
var result = UpdateStrategy(indicatorType, name, period, fastPeriods, slowPeriods,
var result = await UpdateStrategy(indicatorType, name, period, fastPeriods, slowPeriods,
signalPeriods, multiplier, stochPeriods, smoothPeriods, cyclePeriods);
return result;

View File

@@ -127,7 +127,6 @@ public class MessengerService : IMessengerService
// If user is provided, also send to webhook
if (user != null)
{
user = _userService.GetUser(user.Name);
await _webhookService.SendTradeNotification(user, message, isBadBehavior);
}
}

View File

@@ -32,18 +32,7 @@ public class SettingsService : ISettingsService
throw new Exception("Cannot delete all backtests");
}
if (!_scenarioService.DeleteScenarios())
{
throw new Exception("Cannot delete scenarios");
}
if (!_scenarioService.DeleteStrategies())
{
throw new Exception("Cannot delete all strategies");
}
if (!SetupSettings())
if (!await SetupSettings())
{
throw new Exception("Cannot setup settings");
}
@@ -51,7 +40,7 @@ public class SettingsService : ISettingsService
return await Task.FromResult(true);
}
public bool SetupSettings()
public async Task<bool> SetupSettings()
{
try
{
@@ -59,7 +48,7 @@ public class SettingsService : ISettingsService
// SetupMoneyManagementsSeed(Timeframe.FifteenMinutes);
// SetupMoneyManagementsSeed(Timeframe.OneHour);
// SetupMoneyManagementsSeed(Timeframe.OneDay);
SetupScenariosSeed();
await SetupScenariosSeed();
}
catch (Exception ex)
{
@@ -85,107 +74,107 @@ public class SettingsService : ISettingsService
// await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement);
// }
private void SetupScenariosSeed()
private async Task SetupScenariosSeed()
{
SetupMacd();
SetupRsiDiv();
SetupRsiDivConfirm();
SetupSuperTrend();
SetupChandelierExit();
SetupStochRsiTrend();
SetupStochSTCTrend();
SetupEmaTrend();
SetupEmaCross();
await SetupMacd();
await SetupRsiDiv();
await SetupRsiDivConfirm();
await SetupSuperTrend();
await SetupChandelierExit();
await SetupStochRsiTrend();
await SetupStochSTCTrend();
await SetupEmaTrend();
await SetupEmaCross();
}
private void SetupStochSTCTrend()
private async Task SetupStochSTCTrend()
{
var name = "STCTrend";
var strategy = _scenarioService.CreateStrategy(IndicatorType.Stc,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.Stc,
name,
fastPeriods: 23,
slowPeriods: 50,
cyclePeriods: 10);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
private void SetupMacd()
private async Task SetupMacd()
{
var name = "MacdCross";
var strategy = _scenarioService.CreateStrategy(IndicatorType.MacdCross,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.MacdCross,
name,
fastPeriods: 12,
slowPeriods: 26,
signalPeriods: 9);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
private void SetupRsiDiv()
private async Task SetupRsiDiv()
{
var name = "RsiDiv6";
var strategy = _scenarioService.CreateStrategy(IndicatorType.RsiDivergence,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.RsiDivergence,
name,
period: 6);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
private void SetupRsiDivConfirm()
private async Task SetupRsiDivConfirm()
{
var name = "RsiDivConfirm6";
var strategy = _scenarioService.CreateStrategy(IndicatorType.RsiDivergenceConfirm,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.RsiDivergenceConfirm,
name,
period: 6);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
private void SetupSuperTrend()
private async Task SetupSuperTrend()
{
var name = "SuperTrend";
var strategy = _scenarioService.CreateStrategy(IndicatorType.SuperTrend,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.SuperTrend,
name,
period: 10,
multiplier: 3);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
private void SetupChandelierExit()
private async Task SetupChandelierExit()
{
var name = "ChandelierExit";
var strategy = _scenarioService.CreateStrategy(IndicatorType.ChandelierExit,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.ChandelierExit,
name,
period: 22,
multiplier: 3);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
private void SetupStochRsiTrend()
private async Task SetupStochRsiTrend()
{
var name = "StochRsiTrend";
var strategy = _scenarioService.CreateStrategy(IndicatorType.StochRsiTrend,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.StochRsiTrend,
name,
period: 14,
stochPeriods: 14,
signalPeriods: 3,
smoothPeriods: 1);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
private void SetupEmaTrend()
private async Task SetupEmaTrend()
{
var name = "Ema200Trend";
var strategy = _scenarioService.CreateStrategy(IndicatorType.EmaTrend,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.EmaTrend,
name,
period: 200);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
private void SetupEmaCross()
private async Task SetupEmaCross()
{
var name = "Ema200Cross";
var strategy = _scenarioService.CreateStrategy(IndicatorType.EmaCross,
var strategy = await _scenarioService.CreateStrategy(IndicatorType.EmaCross,
name,
period: 200);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
}
public async Task<bool> CreateDefaultConfiguration(User user)
@@ -212,7 +201,7 @@ public class SettingsService : ISettingsService
await _moneyManagementService.CreateOrUpdateMoneyManagement(user, defaultMoneyManagement);
// Create default Strategy (StcTrend)
var defaultStrategy = _scenarioService.CreateIndicatorForUser(
var defaultStrategy = await _scenarioService.CreateIndicatorForUser(
user,
IndicatorType.Stc,
"Stc",
@@ -226,7 +215,7 @@ public class SettingsService : ISettingsService
// Create default Scenario containing the strategy
var strategyNames = new List<string> { defaultStrategy.Name };
var defaultScenario = _scenarioService.CreateScenarioForUser(
var defaultScenario = await _scenarioService.CreateScenarioForUser(
user,
"STC Scenario",
strategyNames

View File

@@ -1,9 +1,7 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Risk;
using Managing.Domain.Strategies;
using Managing.Domain.Synth.Models;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -223,7 +221,7 @@ public class SynthPredictionService : ISynthPredictionService
/// <param name="isBacktest">Whether this is a backtest scenario</param>
/// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
/// <returns>Comprehensive signal validation result including confidence, probabilities, ratio, and blocking status</returns>
public async Task<SignalValidationResult> ValidateSignalAsync(Signal signal, decimal currentPrice,
public async Task<SignalValidationResult> ValidateSignalAsync(LightSignal signal, decimal currentPrice,
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null)
{
var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig);
@@ -925,7 +923,7 @@ public class SynthPredictionService : ISynthPredictionService
/// Estimates liquidation price based on money management settings
/// </summary>
public decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction,
MoneyManagement moneyManagement)
LightMoneyManagement moneyManagement)
{
// This is a simplified estimation - in reality, you'd use the actual money management logic
var riskPercentage = 0.02m; // Default 2% risk

View File

@@ -23,7 +23,7 @@ public class ClosePositionCommandHandler(
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
if (request.Position == null)
{
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result;
_ = await exchangeService.CancelOrder(account, request.Position.Ticker);
return request.Position;
}
@@ -31,7 +31,7 @@ public class ClosePositionCommandHandler(
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
? request.ExecutionPrice.GetValueOrDefault()
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
: await exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
// Check if position still open
if (!request.IsForBacktest)
@@ -46,7 +46,7 @@ public class ClosePositionCommandHandler(
request.Position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice,
request.Position.Open.Leverage);
tradingService.UpdatePosition(request.Position);
await tradingService.UpdatePositionAsync(request.Position);
return request.Position;
}
}
@@ -67,7 +67,7 @@ public class ClosePositionCommandHandler(
request.Position.Open.Leverage);
if (!request.IsForBacktest)
tradingService.UpdatePosition(request.Position);
await tradingService.UpdatePositionAsync(request.Position);
}
return request.Position;

View File

@@ -1,16 +0,0 @@
using Managing.Common;
using Managing.Domain.Trades;
using MediatR;
namespace Managing.Application.Trading.Commands
{
public class GetPositionsCommand : IRequest<List<Position>>
{
public GetPositionsCommand(Enums.PositionInitiator initiator)
{
Initiator = initiator;
}
public Enums.PositionInitiator Initiator { get; internal set; }
}
}

View File

@@ -1,5 +1,4 @@
using Managing.Domain.MoneyManagements;
using Managing.Domain.Trades;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using MediatR;
using static Managing.Common.Enums;
@@ -10,7 +9,7 @@ namespace Managing.Application.Trading.Commands
{
public OpenPositionRequest(
string accountName,
MoneyManagement moneyManagement,
LightMoneyManagement moneyManagement,
TradeDirection direction,
Ticker ticker,
PositionInitiator initiator,
@@ -43,7 +42,7 @@ namespace Managing.Application.Trading.Commands
public string SignalIdentifier { get; set; }
public string AccountName { get; }
public MoneyManagement MoneyManagement { get; }
public LightMoneyManagement MoneyManagement { get; }
public TradeDirection Direction { get; }
public Ticker Ticker { get; }
public bool IsForPaperTrading { get; }

View File

@@ -1,23 +0,0 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands;
using Managing.Domain.Trades;
using MediatR;
namespace Managing.Application.Trading
{
public class GetPositionsCommandHandler : IRequestHandler<GetPositionsCommand, List<Position>>
{
private readonly ITradingService _tradingService;
public GetPositionsCommandHandler(ITradingService tradingService)
{
_tradingService = tradingService;
}
public Task<List<Position>> Handle(GetPositionsCommand request, CancellationToken cancellationToken)
{
var positions = _tradingService.GetPositions(request.Initiator);
return Task.FromResult(positions.ToList());
}
}
}

View File

@@ -15,9 +15,9 @@ public class GetTradeCommandHandler : IRequestHandler<GetTradeCommand, Trade>
_accountService = accountService;
}
public Task<Trade> Handle(GetTradeCommand request, CancellationToken cancellationToken)
public async Task<Trade> Handle(GetTradeCommand request, CancellationToken cancellationToken)
{
var account = _accountService.GetAccount(request.AccountName, true, false).Result;
return _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker);
var account = await _accountService.GetAccount(request.AccountName, true, false);
return await _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker);
}
}

View File

@@ -16,10 +16,10 @@ namespace Managing.Application.Trading
_accountService = accountService;
}
public Task<List<Trade>> Handle(GetTradesCommand request, CancellationToken cancellationToken)
public async Task<List<Trade>> Handle(GetTradesCommand request, CancellationToken cancellationToken)
{
var account = _accountService.GetAccount(request.AccountName, true, false).Result;
return _exchangeService.GetTrades(account, request.Ticker);
var account = await _accountService.GetAccount(request.AccountName, true, false);
return await _exchangeService.GetTrades(account, request.Ticker);
}
}
}

View File

@@ -49,12 +49,12 @@ namespace Managing.Application.Trading
var price = request.IsForPaperTrading && request.Price.HasValue
? request.Price.Value
: exchangeService.GetPrice(account, request.Ticker, DateTime.Now);
: await exchangeService.GetPrice(account, request.Ticker, DateTime.Now);
var quantity = balanceToRisk / price;
var openPrice = request.IsForPaperTrading || request.Price.HasValue
? request.Price.Value
: exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
: await exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
// Determine SL/TP Prices
var stopLossPrice = RiskHelpers.GetStopLossPrice(request.Direction, openPrice, request.MoneyManagement);
@@ -107,22 +107,12 @@ namespace Managing.Application.Trading
if (!request.IsForPaperTrading)
{
tradingService.InsertPosition(position);
await tradingService.InsertPositionAsync(position);
}
return position;
}
private static TradeStatus GetExpectedStatus(OpenPositionRequest request)
{
if (request.IsForPaperTrading)
{
return TradeStatus.Filled;
}
return TradeStatus.Requested;
}
private static bool IsOpenTradeHandled(TradeStatus tradeStatus, TradingExchanges exchange)
{
return tradeStatus == TradeStatus.Filled

View File

@@ -51,85 +51,70 @@ public class TradingService : ITradingService
_synthPredictionService = synthPredictionService;
}
public void DeleteScenario(string name)
public async Task DeleteScenarioAsync(string name)
{
_tradingRepository.DeleteScenario(name);
await _tradingRepository.DeleteScenarioAsync(name);
}
public void DeleteScenarios()
public async Task DeleteStrategyAsync(string name)
{
_tradingRepository.DeleteScenarios();
await _tradingRepository.DeleteIndicatorAsync(name);
}
public void DeleteStrategies()
public async Task<Position> GetPositionByIdentifierAsync(string identifier)
{
_tradingRepository.DeleteIndicators();
return await _tradingRepository.GetPositionByIdentifierAsync(identifier);
}
public void DeleteStrategy(string name)
public async Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator)
{
_tradingRepository.DeleteIndicator(name);
return await _tradingRepository.GetPositionsAsync(positionInitiator);
}
public Position GetPositionByIdentifier(string identifier)
public async Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus postionStatus)
{
return _tradingRepository.GetPositionByIdentifier(identifier);
}
public IEnumerable<Position> GetPositions(PositionInitiator positionInitiator)
{
return _tradingRepository.GetPositions(positionInitiator);
}
public IEnumerable<Position> GetPositionsByStatus(PositionStatus postionStatus)
{
return _tradingRepository.GetPositionsByStatus(postionStatus);
return await _tradingRepository.GetPositionsByStatusAsync(postionStatus);
}
public Scenario GetScenarioByName(string scenario)
public async Task<Scenario> GetScenarioByNameAsync(string scenario)
{
return _tradingRepository.GetScenarioByName(scenario);
return await _tradingRepository.GetScenarioByNameAsync(scenario);
}
public IEnumerable<Scenario> GetScenarios()
public async Task<IEnumerable<Scenario>> GetScenariosAsync()
{
return _tradingRepository.GetScenarios();
return await _tradingRepository.GetScenariosAsync();
}
public IEnumerable<Indicator> GetStrategies()
public async Task<IEnumerable<Indicator>> GetStrategiesAsync()
{
return _tradingRepository.GetIndicators();
return await _tradingRepository.GetStrategiesAsync();
}
public Indicator GetStrategyByName(string strategy)
public async Task<Indicator> GetStrategyByNameAsync(string strategy)
{
return _tradingRepository.GetStrategyByName(strategy);
return await _tradingRepository.GetStrategyByNameAsync(strategy);
}
public void InsertPosition(Position position)
public async Task InsertPositionAsync(Position position)
{
_tradingRepository.InsertPosition(position);
await _tradingRepository.InsertPositionAsync(position);
}
public void InsertScenario(Scenario scenario)
public async Task InsertScenarioAsync(Scenario scenario)
{
_tradingRepository.InsertScenario(scenario);
await _tradingRepository.InsertScenarioAsync(scenario);
}
public void InsertSignal(Signal signal)
public async Task InsertStrategyAsync(Indicator indicator)
{
_tradingRepository.InsertSignal(signal);
}
public void InsertStrategy(Indicator indicator)
{
_tradingRepository.InsertStrategy(indicator);
await _tradingRepository.InsertStrategyAsync(indicator);
}
public async Task<Position> ManagePosition(Account account, Position position)
{
var lastPrice = _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow);
var lastPrice = await _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow);
var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker);
var orders = await _exchangeService.GetOpenOrders(account, position.Ticker);
@@ -184,54 +169,19 @@ public class TradingService : ITradingService
return position;
}
public void UpdateFee(TradingExchanges exchange)
{
var lastFee = _tradingRepository.GetFee(exchange);
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange);
if (lastFee != null)
{
if (DateTime.UtcNow.AddHours(-6) >= lastFee.LastUpdate)
{
lastFee.Cost = _exchangeService.GetFee(account);
lastFee.LastUpdate = DateTime.UtcNow;
_tradingRepository.UpdateFee(lastFee);
}
}
else
{
lastFee = new Fee
{
Cost = _exchangeService.GetFee(account),
Exchange = exchange,
LastUpdate = DateTime.UtcNow
};
_tradingRepository.InsertFee(lastFee);
}
public async Task UpdatePositionAsync(Position position)
{
await _tradingRepository.UpdatePositionAsync(position);
}
public decimal GetFee(Account account, bool isForPaperTrading = false)
{
if (isForPaperTrading && account.Exchange != TradingExchanges.Evm)
{
return 0.000665M;
}
return _cacheService.GetOrSave($"Fee-{account.Exchange}",
() => { return (decimal)_tradingRepository.GetFee(TradingExchanges.Evm)?.Cost; }, TimeSpan.FromHours(2));
}
public void UpdatePosition(Position position)
{
_tradingRepository.UpdatePosition(position);
}
public IEnumerable<Position> GetPositions()
public async Task<IEnumerable<Position>> GetPositionsAsync()
{
var positions = new List<Position>();
positions.AddRange(GetPositionsByStatus(PositionStatus.New));
positions.AddRange(GetPositionsByStatus(PositionStatus.Filled));
positions.AddRange(GetPositionsByStatus(PositionStatus.PartiallyFilled));
positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.New));
positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.Filled));
positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.PartiallyFilled));
return positions;
}
@@ -239,7 +189,7 @@ public class TradingService : ITradingService
public async Task WatchTrader()
{
var availableTickers = new List<Ticker> { Ticker.BTC, Ticker.ETH, Ticker.UNI, Ticker.LINK };
var watchAccount = GetTradersWatch();
var watchAccount = await GetTradersWatch();
var key = $"AccountsQuantityInPosition";
var aqip = _cacheService.GetValue<List<TraderFollowup>>(key);
@@ -264,10 +214,11 @@ public class TradingService : ITradingService
_cacheService.SaveValue(key, aqip, TimeSpan.FromMinutes(10));
}
public IEnumerable<Trader> GetTradersWatch()
public async Task<IEnumerable<Trader>> GetTradersWatch()
{
var watchAccount = _statisticRepository.GetBestTraders();
var customWatchAccount = _accountService.GetAccounts(true, false).Where(a => a.Type == AccountType.Watch)
var watchAccount = await _statisticRepository.GetBestTradersAsync();
var customWatchAccount = (await _accountService.GetAccountsAsync(true, false))
.Where(a => a.Type == AccountType.Watch)
.ToList().MapToTraders();
watchAccount.AddRange(customWatchAccount.Where(a =>
!watchAccount.Any(w => w.Address.Equals(a.Address, StringComparison.InvariantCultureIgnoreCase))));
@@ -279,14 +230,14 @@ public class TradingService : ITradingService
var fundingRates = _exchangeService.GetFundingRates();
}
public void UpdateScenario(Scenario scenario)
public async Task UpdateScenarioAsync(Scenario scenario)
{
_tradingRepository.UpdateScenario(scenario);
await _tradingRepository.UpdateScenarioAsync(scenario);
}
public void UpdateStrategy(Indicator indicator)
public async Task UpdateStrategyAsync(Indicator indicator)
{
_tradingRepository.UpdateStrategy(indicator);
await _tradingRepository.UpdateStrategyAsync(indicator);
}
public async Task<IEnumerable<Position>> GetBrokerPositions(Account account)
@@ -407,7 +358,7 @@ public class TradingService : ITradingService
}
// Synth API integration methods
public async Task<SignalValidationResult> ValidateSynthSignalAsync(Signal signal, decimal currentPrice,
public async Task<SignalValidationResult> ValidateSynthSignalAsync(LightSignal signal, decimal currentPrice,
TradingBotConfig botConfig, bool isBacktest)
{
return await _synthPredictionService.ValidateSignalAsync(signal, currentPrice, botConfig, isBacktest);
@@ -433,12 +384,12 @@ public class TradingService : ITradingService
/// <param name="scenario">The scenario containing indicators.</param>
/// <param name="candles">The candles to calculate indicators for.</param>
/// <returns>A dictionary of indicator types to their calculated values.</returns>
public async Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
Scenario scenario,
public Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValuesAsync(
Scenario scenario,
List<Candle> candles)
{
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
if (scenario?.Indicators == null || scenario.Indicators.Count == 0)
{
return indicatorsValues;
@@ -459,13 +410,13 @@ public class TradingService : ITradingService
// Build the indicator using ScenarioHelpers
var builtIndicator = ScenarioHelpers.BuildIndicator(indicator, 10000);
builtIndicator.Candles = fixedCandles;
indicatorsValues[indicator.Type] = builtIndicator.GetIndicatorValues();
}
catch (Exception ex)
{
// Log the error but continue with other indicators
_logger.LogError(ex, "Error calculating indicator {IndicatorName}: {ErrorMessage}",
_logger.LogError(ex, "Error calculating indicator {IndicatorName}: {ErrorMessage}",
indicator.Name, ex.Message);
}
}

View File

@@ -14,6 +14,7 @@ public class UserService : IUserService
private readonly IUserRepository _userRepository;
private readonly IAccountService _accountService;
private readonly ILogger<UserService> _logger;
private readonly ICacheService _cacheService;
private string[] authorizedAddresses =
[
@@ -32,12 +33,15 @@ public class UserService : IUserService
public UserService(
IEvmManager evmManager,
IUserRepository userRepository,
IAccountService accountService, ILogger<UserService> logger)
IAccountService accountService,
ILogger<UserService> logger,
ICacheService cacheService)
{
_evmManager = evmManager;
_userRepository = userRepository;
_accountService = accountService;
_logger = logger;
_cacheService = cacheService;
}
public async Task<User> Authenticate(string name, string address, string message, string signature)
@@ -116,16 +120,27 @@ public class UserService : IUserService
return user;
}
public User GetUser(string name)
{
return _userRepository.GetUserByNameAsync(name).Result;
}
public async Task<User> GetUserByAddressAsync(string address)
{
var cacheKey = $"user-by-address-{address}";
// Check cache first
var cachedUser = _cacheService.GetValue<User>(cacheKey);
if (cachedUser != null)
{
return cachedUser;
}
// Cache miss - fetch from database
var account = await _accountService.GetAccountByKey(address, true, false);
var user = await _userRepository.GetUserByNameAsync(account.User.Name);
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
// Use proper async version to avoid DbContext concurrency issues
user.Accounts = (await _accountService.GetAccountsByUserAsync(user)).ToList();
// Save to cache for 10 minutes (JWT middleware calls this on every request)
_cacheService.SaveValue(cacheKey, user, TimeSpan.FromMinutes(10));
return user;
}
@@ -148,7 +163,7 @@ public class UserService : IUserService
public async Task<User> UpdateAvatarUrl(User user, string avatarUrl)
{
// Validate URL format and image extension
if (!Uri.TryCreate(avatarUrl, UriKind.Absolute, out Uri? uriResult) ||
if (!Uri.TryCreate(avatarUrl, UriKind.Absolute, out Uri? uriResult) ||
(uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps))
{
throw new Exception("Invalid URL format");
@@ -182,4 +197,9 @@ public class UserService : IUserService
await _userRepository.UpdateUser(user);
return user;
}
public async Task<User> GetUser(string name)
{
return await _userRepository.GetUserByNameAsync(name);
}
}

View File

@@ -2,7 +2,6 @@ using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.ManageBot.Commands;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Statistics;
using MediatR;
using Microsoft.Extensions.Logging;
@@ -19,15 +18,15 @@ public class BalanceTrackingWorker : BaseWorker<BalanceTrackingWorker>
public BalanceTrackingWorker(
ILogger<BalanceTrackingWorker> logger,
IServiceProvider serviceProvider,
IMediator mediator,
IAccountService accountService,
IAgentBalanceRepository agentBalanceRepository,
IWorkerService workerService)
IAgentBalanceRepository agentBalanceRepository)
: base(
WorkerType.BalanceTracking,
logger,
TimeSpan.FromHours(1),
workerService)
serviceProvider)
{
_mediator = mediator;
_accountService = accountService;

View File

@@ -1,5 +1,4 @@
using Managing.Application.ManageBot;
using Managing.Application.Workers.Abstractions;
using MediatR;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -8,12 +7,12 @@ namespace Managing.Application.Workers;
public class BotManagerWorker(
ILogger<BotManagerWorker> logger,
IMediator mediadior,
IWorkerService workerService)
IServiceProvider serviceProvider,
IMediator mediadior)
: BaseWorker<BotManagerWorker>(WorkerType.BotManager,
logger,
TimeSpan.FromMinutes(5),
workerService)
serviceProvider)
{
protected override async Task Run(CancellationToken cancellationToken)
{

View File

@@ -1,9 +1,9 @@
using System.Collections.Concurrent;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Backtests;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -11,18 +11,17 @@ namespace Managing.Application.Workers;
public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
{
private readonly IBacktester _backtester;
private readonly IServiceProvider _serviceProvider;
private readonly IHubContext<BacktestHub> _hubContext;
private readonly ConcurrentDictionary<string, HashSet<string>> _sentBacktestIds = new();
public NotifyBundleBacktestWorker(
IBacktester backtester,
IServiceProvider serviceProvider,
IHubContext<BacktestHub> hubContext,
ILogger<NotifyBundleBacktestWorker> logger,
IWorkerService workerService)
: base(WorkerType.NotifyBundleBacktest, logger, TimeSpan.FromMinutes(1), workerService)
ILogger<NotifyBundleBacktestWorker> logger)
: base(WorkerType.NotifyBundleBacktest, logger, TimeSpan.FromMinutes(1), serviceProvider)
{
_backtester = backtester;
_serviceProvider = serviceProvider;
_hubContext = hubContext;
}
@@ -30,8 +29,12 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
{
try
{
// Create a new service scope to get fresh instances of services with scoped DbContext
using var scope = _serviceProvider.CreateScope();
var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();
// Fetch all running bundle requests
var runningBundles = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Running);
var runningBundles = await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Running);
foreach (var bundle in runningBundles)
{
@@ -39,7 +42,7 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
if (string.IsNullOrEmpty(requestId)) continue;
// Fetch all backtests for this bundle
var (backtests, _) = _backtester.GetBacktestsByRequestIdPaginated(requestId, 1, 100);
var (backtests, _) = backtester.GetBacktestsByRequestIdPaginated(requestId, 1, 100);
if (!_sentBacktestIds.ContainsKey(requestId))
_sentBacktestIds[requestId] = new HashSet<string>();

View File

@@ -1,56 +0,0 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Workflows.Flows.Feeds;
using Managing.Application.Workflows.Flows.Strategies;
using Managing.Application.Workflows.Flows.Trading;
using Managing.Domain.Workflows;
using Managing.Domain.Workflows.Synthetics;
using static Managing.Common.Enums;
namespace Managing.Application.Workflows;
public class FlowFactory : IFlowFactory
{
private readonly IExchangeService _exchangeService;
private readonly ICacheService _cacheService;
private readonly ITradingService _tradingService;
private readonly IAccountService _accountService;
public FlowFactory(IExchangeService exchangeService, ICacheService cacheService, ITradingService tradingService,
IAccountService accountService)
{
_exchangeService = exchangeService;
_cacheService = cacheService;
_tradingService = tradingService;
_accountService = accountService;
}
public IFlow BuildFlow(SyntheticFlow request)
{
IFlow flow = request.Type switch
{
FlowType.FeedTicker => new FeedTicker(_exchangeService),
FlowType.RsiDivergence => new RsiDiv(),
FlowType.OpenPosition => new OpenPosition(_exchangeService, _cacheService, _accountService,
_tradingService),
_ => throw new NotImplementedException(),
};
flow.Children = new List<IFlow>();
flow.Parameters = new List<FlowParameter>();
foreach (var parameter in request.Parameters)
{
if (!flow.Parameters.Any(p => p.Name == parameter.Name))
{
flow.Parameters.Add(new FlowParameter
{
Name = parameter.Name,
Value = parameter.Value
});
}
}
return flow;
}
}

View File

@@ -1,64 +0,0 @@
using Managing.Application.Abstractions.Services;
using Managing.Domain.Workflows;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.Workflows.Flows.Feeds;
public class FeedTicker : FlowBase
{
public override List<IFlow> Children { get; set; }
public override List<FlowParameter> Parameters { get; set; }
public override Guid ParentId { get; }
public override Guid Id { get; }
public override string Output { get; set; }
public override string Name => "Feed Ticker";
public override FlowType Type => FlowType.FeedTicker;
public override string Description => "This flow will take a feed and output the candles";
public FeedTickerParameters FeedTickerParameters { get; set; }
public override List<FlowOutput> AcceptedInputs => new();
public override List<FlowOutput> OutputTypes => new() { FlowOutput.Candles };
private readonly IExchangeService _exchangeService;
public FeedTicker(IExchangeService exchangeService)
{
_exchangeService = exchangeService;
}
public async override Task Execute(string input)
{
MapParameters();
var candles = await _exchangeService.GetCandlesInflux(FeedTickerParameters.Exchange, FeedTickerParameters.Ticker, DateTime.Now.AddDays(-11), FeedTickerParameters.Timeframe);
Output = JsonConvert.SerializeObject(candles.ToHashSet());
if(Children != null && Children.Count > 0)
{
foreach (var child in Children)
{
await child.Execute(Output);
}
}
}
public override void MapParameters()
{
FeedTickerParameters = new FeedTickerParameters();
foreach (var param in Parameters)
{
if (param.Name == nameof(FeedTickerParameters.Ticker))
FeedTickerParameters.Ticker = (Ticker)Enum.Parse(typeof(Ticker), param.Value);
if (param.Name == nameof(FeedTickerParameters.Exchange))
FeedTickerParameters.Exchange = (TradingExchanges)Enum.Parse(typeof(TradingExchanges), param.Value);
if (param.Name == nameof(FeedTickerParameters.Timeframe))
FeedTickerParameters.Timeframe = (Timeframe)Enum.Parse(typeof(Timeframe), param.Value);
}
}
}
public class FeedTickerParameters
{
public TradingExchanges Exchange { get; set; }
public Ticker Ticker { get; set; }
public Timeframe Timeframe { get; set; }
}

View File

@@ -1,65 +0,0 @@
using Managing.Domain.Candles;
using Managing.Domain.Strategies.Signals;
using Managing.Domain.Workflows;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.Workflows.Flows.Strategies;
public class RsiDiv : FlowBase
{
public override List<IFlow> Children { get; set; }
public override List<FlowParameter> Parameters { get; set; }
public override Guid ParentId { get; }
public override Guid Id { get; }
public override string Output { get; set; }
public override string Name => "Rsi Divergence";
public override string Description => "This flow will take a feed of candle an run the RSI Divergence";
public override FlowType Type => FlowType.RsiDivergence;
public override List<FlowOutput> AcceptedInputs => new() { FlowOutput.Candles };
public override List<FlowOutput> OutputTypes => new() { FlowOutput.Signal };
public RsiDivParameters RsiDivParameters { get; set; }
public async override Task Execute(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentException($"'{nameof(input)}' cannot be null or empty.", nameof(input));
}
MapParameters();
var candles = JsonConvert.DeserializeObject<HashSet<Candle>>(input);
var strategy = new RsiDivergenceIndicator(Name, RsiDivParameters.Period);
strategy.UpdateCandles(candles);
strategy.Run();
Output = JsonConvert.SerializeObject(strategy.Signals);
if (Children != null && Children.Count > 0)
{
foreach (var child in Children)
{
await child.Execute(Output);
}
}
}
public override void MapParameters()
{
RsiDivParameters = new RsiDivParameters();
foreach (var param in Parameters)
{
if (param.Name == nameof(RsiDivParameters.Period))
RsiDivParameters.Period = int.Parse(param.Value);
if (param.Name == nameof(RsiDivParameters.Timeframe))
RsiDivParameters.Timeframe = (Timeframe)Enum.Parse(typeof(Timeframe), param.Value);
}
}
}
public class RsiDivParameters
{
public int Period { get; set; }
public Timeframe Timeframe { get; set; }
}

View File

@@ -1,232 +0,0 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading;
using Managing.Application.Trading.Commands;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Managing.Domain.Workflows;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.Workflows.Flows.Trading;
public class OpenPosition : FlowBase
{
public override List<IFlow> Children { get; set; }
public override List<FlowParameter> Parameters { get; set; }
public override Guid ParentId { get; }
public override Guid Id { get; }
public override string Output { get; set; }
public override string Name => "Open Position";
public override FlowType Type => FlowType.OpenPosition;
public override string Description => "This flow will open a position for a given signal";
public OpenPositionParameters OpenPositionParameters { get; set; }
public override List<FlowOutput> AcceptedInputs => new() { FlowOutput.Signal, FlowOutput.MoneyManagement };
public override List<FlowOutput> OutputTypes => new() { FlowOutput.Position };
private readonly IExchangeService _exchangeService;
private readonly ICacheService _cacheService;
private readonly IAccountService _accountService;
private readonly ITradingService _tradingService;
private readonly ISettingsRepository _settingsRepository;
private readonly IMessengerService _messengerService;
private readonly string POSITIONS_KEY = "positions";
private readonly string ACCOUNT_KEY = "account";
private readonly string CANDLES_KEY = "candles";
private readonly string SIGNALS_KEY = "signals";
private readonly string FEE_KEY = "fee";
private readonly string WALLET_BALANCES = "wallet-balance";
private decimal Fee { get; set; }
public OpenPosition(
IExchangeService exchangeService,
ICacheService cacheService,
IAccountService accountService,
ITradingService tradingService)
{
_exchangeService = exchangeService;
_cacheService = cacheService;
_accountService = accountService;
_tradingService = tradingService;
}
public async override Task Execute(string input)
{
MapParameters();
var signal = JsonConvert.DeserializeObject<Signal>(input);
var Candles = JsonConvert.DeserializeObject<HashSet<Candle>>(_cacheService.GetValue(CANDLES_KEY));
var Account = JsonConvert.DeserializeObject<Account>(_cacheService.GetValue(ACCOUNT_KEY));
var Positions = JsonConvert.DeserializeObject<List<Position>>(_cacheService.GetValue(POSITIONS_KEY));
var Signals = JsonConvert.DeserializeObject<HashSet<Signal>>(_cacheService.GetValue(POSITIONS_KEY));
await ExecuteOpenPosition(signal, Positions, Signals, Candles, Account);
_cacheService.SaveValue(POSITIONS_KEY, JsonConvert.SerializeObject(Positions));
_cacheService.SaveValue(SIGNALS_KEY, JsonConvert.SerializeObject(Signals));
if (Children != null && Children.Count > 0)
{
foreach (var child in Children)
{
await child.Execute(Output);
}
}
}
private async Task ExecuteOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals,
HashSet<Candle> candles, Account account)
{
// Check if a position is already open
var openedPosition = positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
&& p.SignalIdentifier != signal.Identifier);
var lastPrice = OpenPositionParameters.IsForBacktest
? candles.Last().Close
: _exchangeService.GetPrice(account, signal.Ticker, DateTime.UtcNow);
// If position open
if (openedPosition != null)
{
var previousSignal = signals.First(s => s.Identifier == openedPosition.SignalIdentifier);
// Check if signal is the opposite side => flip the position
if (openedPosition.OriginDirection == signal.Direction)
{
// An operation is already open for the same direction
//await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction");
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
}
else
{
// An operation is already open for the opposite direction
// ==> Flip the position
if (OpenPositionParameters.FlipPosition)
{
//await LogInformation("Try to flip the position because of an opposite direction signal");
//await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
positions.FirstOrDefault(s => s.Identifier == previousSignal.Identifier).Status =
PositionStatus.Flipped;
await ExecuteOpenPosition(signal, positions, signals, candles, account);
//await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
}
else
{
//await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
}
}
}
else
{
if (!CanOpenPosition(signal, positions, signals, candles))
{
//await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
return;
}
//await LogInformation($"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}");
try
{
var moneyManagement =
await _settingsRepository.GetMoneyManagement(OpenPositionParameters.MoneyManagementName);
var WalletBalances =
JsonConvert.DeserializeObject<Dictionary<DateTime, decimal>>(
_cacheService.GetValue(WALLET_BALANCES));
var command = new OpenPositionRequest(
OpenPositionParameters.AccountName,
moneyManagement,
signal.Direction,
signal.Ticker,
PositionInitiator.Bot,
signal.Date,
account.User,
100m, // Default bot trading balance
OpenPositionParameters.IsForBacktest,
lastPrice);
var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService)
.Handle(command);
if (position != null)
{
if (position.Open.Status != TradeStatus.Cancelled)
{
position.SignalIdentifier = signal.Identifier;
positions.Add(position);
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status =
SignalStatus.PositionOpen;
if (!OpenPositionParameters.IsForBacktest)
{
await _messengerService.SendPosition(position);
}
Output = JsonConvert.SerializeObject(position);
//Logger.LogInformation($"Position requested");
}
else
{
positions.FirstOrDefault(s => s.Identifier == signal.Identifier).Status =
PositionStatus.Rejected;
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
}
}
}
catch (Exception ex)
{
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
//await LogWarning($"Cannot open trade : {ex.Message}");
}
}
}
private bool CanOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals,
HashSet<Candle> candles)
{
if (positions.Count == 0)
return true;
var lastPosition = positions.LastOrDefault(p => p.IsFinished()
&& p.SignalIdentifier != signal.Identifier
&& p.ProfitAndLoss.Realized < 0
&& p.OriginDirection == signal.Direction);
if (lastPosition == null)
return true;
var tenCandleAgo = candles.TakeLast(10).First();
var positionSignal = signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);
return positionSignal.Date < tenCandleAgo.Date;
}
public override void MapParameters()
{
OpenPositionParameters = new OpenPositionParameters();
foreach (var param in Parameters)
{
if (param.Name == nameof(OpenPositionParameters.AccountName))
OpenPositionParameters.AccountName = param.Value;
if (param.Name == nameof(OpenPositionParameters.MoneyManagementName))
OpenPositionParameters.MoneyManagementName = param.Value;
if (param.Name == nameof(OpenPositionParameters.FlipPosition))
OpenPositionParameters.FlipPosition = bool.Parse(param.Value);
}
}
}
public class OpenPositionParameters
{
public string MoneyManagementName { get; set; }
public string AccountName { get; set; }
public bool IsForBacktest { get; set; }
public bool FlipPosition { get; set; }
}

View File

@@ -1,125 +0,0 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Workflows.Flows.Feeds;
using Managing.Application.Workflows.Flows.Strategies;
using Managing.Application.Workflows.Flows.Trading;
using Managing.Domain.Workflows;
using Managing.Domain.Workflows.Synthetics;
namespace Managing.Application.Workflows;
public class WorkflowService : IWorkflowService
{
private readonly IWorkflowRepository _workflowRepository;
private readonly IExchangeService _exchangeService;
private readonly IFlowFactory _flowFactory;
private readonly ICacheService _cacheService;
private readonly ITradingService _tradingService;
private readonly IAccountService _accountService;
public WorkflowService(
IWorkflowRepository workflowRepository,
IExchangeService exchangeService,
IFlowFactory flowFactory,
ICacheService cacheService,
ITradingService tradingService,
IAccountService accountService)
{
_workflowRepository = workflowRepository;
_exchangeService = exchangeService;
_flowFactory = flowFactory;
_cacheService = cacheService;
_tradingService = tradingService;
_accountService = accountService;
}
public async Task<Workflow> InsertOrUpdateWorkflow(SyntheticWorkflow workflowRequest)
{
var previousWorkflow = await _workflowRepository.GetWorkflow(workflowRequest.Name);
try
{
if (previousWorkflow != null)
{
await _workflowRepository.UpdateWorkflow(workflowRequest);
}
else
{
await _workflowRepository.InsertWorkflow(workflowRequest);
}
}
catch (Exception ex)
{
throw;
}
return Map(workflowRequest);
}
private Workflow Map(SyntheticWorkflow workflowRequest)
{
var workflow = new Workflow
{
Name = workflowRequest.Name,
Usage = workflowRequest.Usage,
Description = workflowRequest.Description,
Flows = new List<IFlow>()
};
// Add first flow that dont have any parent
var firstFlow = workflowRequest.Flows.SingleOrDefault(f => string.IsNullOrEmpty(f.ParentId));
if (firstFlow != null)
{
var flow = Build(workflowRequest.Flows, firstFlow);
workflow.Flows.Add(flow);
}
else
{
// TODO : Throw exception
throw new Exception("No first flow found");
}
return workflow;
}
private IFlow Build(List<SyntheticFlow> flows, SyntheticFlow firstFlow)
{
var flow = _flowFactory.BuildFlow(firstFlow);
foreach (var flowRequest in flows.Where(f => f.ParentId == firstFlow.Id))
{
flow.Children.Add(Build(flows, flowRequest));
}
return flow;
}
public IEnumerable<SyntheticWorkflow> GetWorkflows()
{
return _workflowRepository.GetWorkflows();
}
public bool DeleteWorkflow(string name)
{
return _workflowRepository.DeleteWorkflow(name);
}
public async Task<Workflow> GetWorkflow(string name)
{
return Map(await _workflowRepository.GetWorkflow(name));
}
public Task<IEnumerable<IFlow>> GetAvailableFlows()
{
var availableFlows = new List<IFlow>
{
new FeedTicker(_exchangeService),
new RsiDiv(),
new OpenPosition(_exchangeService, _cacheService, _accountService, _tradingService)
};
return Task.FromResult(availableFlows.AsEnumerable());
}
}