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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
52
src/Managing.Application/ManageBot/BackupBotService.cs
Normal file
52
src/Managing.Application/ManageBot/BackupBotService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user