diff --git a/src/Managing.Api.Workers/Managing.Api.Workers.csproj b/src/Managing.Api.Workers/Managing.Api.Workers.csproj
index 2e99aee..55d4c7a 100644
--- a/src/Managing.Api.Workers/Managing.Api.Workers.csproj
+++ b/src/Managing.Api.Workers/Managing.Api.Workers.csproj
@@ -41,5 +41,8 @@
Always
+
+ Always
+
diff --git a/src/Managing.Api.Workers/Program.cs b/src/Managing.Api.Workers/Program.cs
index 224046a..20d39be 100644
--- a/src/Managing.Api.Workers/Program.cs
+++ b/src/Managing.Api.Workers/Program.cs
@@ -107,16 +107,16 @@ builder.Services.AddSwaggerGen(options =>
});
builder.WebHost.SetupDiscordBot();
-builder.Services.AddHostedService();
+// builder.Services.AddHostedService();
// builder.Services.AddHostedService();
// builder.Services.AddHostedService();
// builder.Services.AddHostedService();
builder.Services.AddHostedService();
builder.Services.AddHostedService();
-// builder.Services.AddHostedService();
+builder.Services.AddHostedService();
builder.Services.AddHostedService();
-builder.Services.AddHostedService();
-builder.Services.AddHostedService();
+// builder.Services.AddHostedService();
+// builder.Services.AddHostedService();
builder.Services.AddHostedService();
builder.Services.AddHostedService();
diff --git a/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs b/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs
index a0e60da..19f4483 100644
--- a/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs
+++ b/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs
@@ -166,49 +166,63 @@ public class PositionManagerWorker : BaseWorker
{
var positions = GetPositions(PositionStatus.New);
- _logger.LogInformation("New positions count : {0} ", positions.Count());
+ _logger.LogInformation("Processing {NewPositionCount} new positions", positions.Count());
foreach (var position in positions)
{
- _logger.LogInformation("Managing position: {0} - Date: {1} - Direction: {2} - Ticker: {3}",
- position.Identifier, position.Date, position.OriginDirection, position.Ticker);
- position.Status = PositionStatus.Updating;
- _tradingService.UpdatePosition(position);
-
- // Update status if position is open since to long
- if (position.Date < DateTime.UtcNow.AddDays(-2))
+ using (_logger.BeginScope("Position {SignalIdentifier}", position.SignalIdentifier))
{
- position.Status = PositionStatus.Canceled;
- _tradingService.UpdatePosition(position);
- _logger.LogInformation($"|_ Position is now Canceled");
- continue;
+ try
+ {
+ // Immediate status update for concurrency protection
+ _logger.LogDebug("[{SignalIdentifier}] Acquiring position lock via status update", position.SignalIdentifier);
+ position.Status = PositionStatus.Updating;
+ _tradingService.UpdatePosition(position);
+
+ var account = await GetAccount(position.AccountName);
+ var trade = await _exchangeService.GetTrade(account.Key, position.Open.ExchangeOrderId, position.Ticker);
+ var openTrade = position.Open;
+
+ if (trade.Status == TradeStatus.PendingOpen || trade.Status == TradeStatus.Requested)
+ {
+ // Position staleness check
+ if (position.Date < DateTime.UtcNow.AddDays(-1))
+ {
+ position.Status = PositionStatus.Canceled;
+ _tradingService.UpdatePosition(position);
+ _logger.LogWarning("[{SignalIdentifier}] Position canceled - stale since {PositionAge} days",
+ position.SignalIdentifier,
+ (DateTime.UtcNow - position.Date).TotalDays);
+ }
+ else
+ {
+ // Reset status for retry
+ position.Status = PositionStatus.New;
+ _tradingService.UpdatePosition(position);
+ _logger.LogInformation("[{SignalIdentifier}] Awaiting order fill - {Ticker} (0/{ExpectedQuantity})",
+ position.SignalIdentifier,
+ position.Ticker, openTrade.Quantity);
+ }
+ }
+ else
+ {
+ position.Status = PositionStatus.PartiallyFilled;
+ position.Open = openTrade;
+ // Position is now open, now waiting to open SLTP
+ _tradingService.UpdatePosition(position);
+
+ _logger.LogInformation("[{SignalIdentifier}] Position now open ",
+ position.SignalIdentifier);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing position {SignalIdentifier}", position.SignalIdentifier);
+ // Consider resetting to New status for retry if needed
+ position.Status = PositionStatus.New;
+ _tradingService.UpdatePosition(position);
+ }
}
-
- var account = await GetAccount(position.AccountName);
- var currentOpenOrders = await _exchangeService.GetOpenOrders(account, position.Ticker);
- // if (currentOpenOrders.Any())
- // {
- // position.Status = PositionStatus.Canceled;
- // _tradingService.UpdatePosition(position);
- // _logger.LogInformation($"|_ Position is now Canceled - Position close from exchange");
- // continue;
- // }
-
- var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker);
-
- if (quantityInPosition <= 0)
- {
- position.Status = PositionStatus.New;
- _logger.LogInformation("|_ Position is currently waiting for filling");
- }
- else
- {
- position.Open.SetStatus(TradeStatus.Filled);
- position.Status = PositionStatus.PartiallyFilled;
- _logger.LogInformation($"|_ Position is now PartiallyFilled");
- }
-
- _tradingService.UpdatePosition(position);
}
}
diff --git a/src/Managing.Api.Workers/appsettings.ProdLocal.json b/src/Managing.Api.Workers/appsettings.ProdLocal.json
new file mode 100644
index 0000000..bac1dba
--- /dev/null
+++ b/src/Managing.Api.Workers/appsettings.ProdLocal.json
@@ -0,0 +1,24 @@
+{
+ "ManagingDatabase": {
+ "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256",
+ "DatabaseName": "ManagingDb"
+ },
+ "InfluxDb": {
+ "Url": "https://influx-db.apps.managing.live",
+ "Organization": "managing-org",
+ "Token": "eOuXcXhH7CS13Iw4CTiDDpRjIjQtEVPOloD82pLPOejI4n0BsEj1YzUw0g3Cs1mdDG5m-RaxCavCMsVTtS5wIQ=="
+ },
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "Microsoft": "Information",
+ "System": "Warning"
+ }
+ }
+ },
+ "ElasticConfiguration": {
+ "Uri": "http://elasticsearch:9200"
+ },
+ "AllowedHosts": "*"
+}
\ No newline at end of file
diff --git a/src/Managing.Api/Managing.Api.csproj b/src/Managing.Api/Managing.Api.csproj
index 843053a..950da1c 100644
--- a/src/Managing.Api/Managing.Api.csproj
+++ b/src/Managing.Api/Managing.Api.csproj
@@ -42,5 +42,8 @@
Always
+
+ Always
+
diff --git a/src/Managing.Api/Program.cs b/src/Managing.Api/Program.cs
index 9f28080..d3511c6 100644
--- a/src/Managing.Api/Program.cs
+++ b/src/Managing.Api/Program.cs
@@ -3,6 +3,7 @@ using System.Text.Json.Serialization;
using Managing.Api.Authorization;
using Managing.Api.Exceptions;
using Managing.Api.Filters;
+using Managing.Api.Workers;
using Managing.Application.Hubs;
using Managing.Bootstrap;
using Managing.Common;
diff --git a/src/Managing.Api/appsettings.ProdLocal.json b/src/Managing.Api/appsettings.ProdLocal.json
new file mode 100644
index 0000000..bac1dba
--- /dev/null
+++ b/src/Managing.Api/appsettings.ProdLocal.json
@@ -0,0 +1,24 @@
+{
+ "ManagingDatabase": {
+ "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256",
+ "DatabaseName": "ManagingDb"
+ },
+ "InfluxDb": {
+ "Url": "https://influx-db.apps.managing.live",
+ "Organization": "managing-org",
+ "Token": "eOuXcXhH7CS13Iw4CTiDDpRjIjQtEVPOloD82pLPOejI4n0BsEj1YzUw0g3Cs1mdDG5m-RaxCavCMsVTtS5wIQ=="
+ },
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "Microsoft": "Information",
+ "System": "Warning"
+ }
+ }
+ },
+ "ElasticConfiguration": {
+ "Uri": "http://elasticsearch:9200"
+ },
+ "AllowedHosts": "*"
+}
\ No newline at end of file
diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs
index ebca3f4..ff06e14 100644
--- a/src/Managing.Application/Bots/TradingBot.cs
+++ b/src/Managing.Application/Bots/TradingBot.cs
@@ -111,8 +111,10 @@ public class TradingBot : Bot, ITradingBot
try
{
- await MessengerService.SendMessage(
- $"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies.");
+ // await MessengerService.SendMessage(
+ // $"Hey everyone! I'm about to start {Name}. 🚀\n" +
+ // $"I'll post an update here each time a signal is triggered by the following strategies: {string.Join(", ", Strategies.Select(s => s.Name))}."
+ // );
}
catch (Exception ex)
{
@@ -185,7 +187,7 @@ public class TradingBot : Bot, ITradingBot
if (!IsForWatchingOnly)
await ManagePositions();
-
+
if (!IsForBacktest)
{
SaveBackup();
@@ -414,11 +416,11 @@ public class TradingBot : Bot, ITradingBot
await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
// if position is not open
// Re-open the trade for the signal only if signal still up
- //if (signal.Status == SignalStatus.PositionOpen)
- //{
- // Logger.LogInformation($"Try to re-open position");
- // OpenPosition(signal);
- //}
+ if (signal.Status == SignalStatus.PositionOpen)
+ {
+ Logger.LogInformation($"Try to re-open position");
+ await OpenPosition(signal);
+ }
}
}
catch (Exception ex)
@@ -495,7 +497,7 @@ public class TradingBot : Bot, ITradingBot
MoneyManagement,
signal.Direction,
Ticker,
- PositionInitiator.Bot,
+ IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot ,
signal.Date,
IsForBacktest,
lastPrice,
diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs
index bd266eb..0f1c11e 100644
--- a/src/Managing.Application/ManageBot/BotService.cs
+++ b/src/Managing.Application/ManageBot/BotService.cs
@@ -131,7 +131,7 @@ namespace Managing.Application.ManageBot
scalpingBotData.ScenarioName,
scalpingBotData.Timeframe,
scalpingBotData.IsForWatchingOnly);
- botTask = Task.Run(() => ((ITradingBot)bot).Start());
+ botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
break;
case Enums.BotType.FlippingBot:
var flippingBotData = JsonConvert.DeserializeObject(backupBot.Data);
@@ -143,7 +143,7 @@ namespace Managing.Application.ManageBot
flippingBotData.ScenarioName,
flippingBotData.Timeframe,
flippingBotData.IsForWatchingOnly);
- botTask = Task.Run(() => ((ITradingBot)bot).Start());
+ botTask = Task.Run(InitBot((ITradingBot)bot, backupBot));
break;
}
@@ -154,6 +154,13 @@ namespace Managing.Application.ManageBot
}
}
+ private static Action InitBot(ITradingBot bot, BotBackup backupBot)
+ {
+ bot.Start();
+ bot.LoadBackup(backupBot);
+ return () => { };
+ }
+
public IBot CreateSimpleBot(string botName, Workflow workflow)
{
return new SimpleBot(botName, _tradingBotLogger, workflow, this);
diff --git a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs
index 9039dd5..a068e3a 100644
--- a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs
+++ b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs
@@ -19,71 +19,67 @@ public class LoadBackupBotCommandHandler : IRequestHandler Handle(LoadBackupBotCommand request, CancellationToken cancellationToken)
+{
+ var backupBots = _botService.GetSavedBots().ToList();
+ _logger.LogInformation("Loading {Count} backup bots.", backupBots.Count);
+
+ var result = new Dictionary();
+ bool anyBackupStarted = false;
+ bool anyBotActive = false;
+
+ foreach (var backupBot in backupBots)
{
- BotStatus botStatus = BotStatus.Down;
- var backupBots = _botService.GetSavedBots().ToList();
- var activeBots = _botService.GetActiveBots();
- var result = new Dictionary();
-
- _logger.LogInformation($"Loading {backupBots.Count()} backup bots");
-
- foreach (var backupBot in backupBots)
+ try
{
- // Check if bot is existing in cache
- try
+ var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.GetName() == backupBot.Name);
+
+ if (activeBot == null)
{
- switch (backupBot.BotType)
+ _logger.LogInformation("No active instance found for bot {BotName}. Starting backup...", backupBot.Name);
+ _botService.StartBotFromBackup(backupBot);
+
+ activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.GetName() == backupBot.Name);
+ if (activeBot != null)
{
- case BotType.SimpleBot:
- var simpleBot = activeBots.FirstOrDefault(b => b.GetName() == backupBot.Name);
- if (simpleBot == null)
- {
- _logger.LogInformation($"Starting backup bot {backupBot.Name}");
- _botService.StartBotFromBackup(backupBot);
- result.Add(simpleBot.GetName(), BotStatus.Backup);
- }
- else
- {
- result.Add(simpleBot.GetName(), MiscExtensions.ParseEnum(simpleBot.GetStatus()));
- }
-
- break;
- case BotType.ScalpingBot:
- case BotType.FlippingBot:
- var scalpingBot = activeBots.FirstOrDefault(b => b.GetName() == backupBot.Name);
- if (scalpingBot == null)
- {
- _logger.LogInformation($"Starting backup bot {backupBot.Name}");
- _botService.StartBotFromBackup(backupBot);
- var bots = _botService.GetActiveBots();
- scalpingBot = bots.FirstOrDefault(b => b.GetName() == backupBot.Name);
- result.Add(scalpingBot.GetName(), BotStatus.Backup);
- }
- else
- {
- result.Add(scalpingBot.GetName(),
- MiscExtensions.ParseEnum(scalpingBot.GetStatus()));
- }
-
- break;
- default:
- result.Add(backupBot.Name, BotStatus.Down);
- break;
+ result[activeBot.GetName()] = BotStatus.Backup;
+ anyBackupStarted = true;
+ _logger.LogInformation("Backup bot {BotName} started successfully.", backupBot.Name);
+ }
+ else
+ {
+ result[backupBot.Name] = BotStatus.Down;
+ _logger.LogWarning("Backup bot {BotName} failed to start.", backupBot.Name);
}
}
- catch (Exception ex)
+ else
{
- _logger.LogError($"Error loading bot {backupBot.Name}", ex.Message);
- _botService.DeleteBotBackup(backupBot.Name);
- result.Add(backupBot.Name, BotStatus.Down);
+ var status = MiscExtensions.ParseEnum(activeBot.GetStatus());
+ result[activeBot.GetName()] = status;
+ anyBotActive = true;
+ _logger.LogInformation("Bot {BotName} is already active with status {Status}.", activeBot.GetName(), status);
}
}
-
- _logger.LogInformation("Bot loading completed");
- _logger.LogInformation($"Bots: {result.Select(b => $"{b.Key} - {b.Value}")}");
-
- return Task.FromResult(botStatus.ToString());
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error loading bot {BotName}. Deleting its backup.", backupBot.Name);
+ _botService.DeleteBotBackup(backupBot.Name);
+ result[backupBot.Name] = BotStatus.Down;
+ }
}
+
+ var summary = string.Join(", ", result.Select(b => $"{b.Key}: {b.Value}"));
+ _logger.LogInformation("Bot loading completed. Summary: {Summary}", summary);
+
+ // Determine final status
+ BotStatus finalStatus = anyBackupStarted
+ ? BotStatus.Backup
+ : anyBotActive ? BotStatus.Up : BotStatus.Down;
+
+ _logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus);
+
+ return Task.FromResult(finalStatus.ToString());
+}
+
}
public class LoadBackupBotCommand : IRequest
diff --git a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs
index 57d58e6..7487349 100644
--- a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs
+++ b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs
@@ -108,7 +108,11 @@ namespace Managing.Application.Trading
position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange)
? position.Status
: PositionStatus.Rejected;
- tradingService.InsertPosition(position);
+
+ if (request.IsForPaperTrading)
+ {
+ tradingService.InsertPosition(position);
+ }
return position;
}
diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs
index 8d408f5..7c04f5c 100644
--- a/src/Managing.Application/Users/UserService.cs
+++ b/src/Managing.Application/Users/UserService.cs
@@ -11,7 +11,14 @@ public class UserService : IUserService
private readonly IUserRepository _userRepository;
private readonly IAccountService _accountService;
- private string[] authorizedAddresses = ["0x6781920674dA695aa5120d95D80c4B1788046806", "0xA2B43AFF0992a47838DF2e6099A8439981f0B717", "0xAD4bcf258852e9d47E580798d312E1a52D59E721", "0xAd6D6c80910096b40e45690506a9f1052e072dCB", "0x309b9235edbe1C6f840816771c6C21aDa6c275EE"];
+ private string[] authorizedAddresses = [
+ "0x6781920674dA695aa5120d95D80c4B1788046806", // Macbook
+ "0xA2B43AFF0992a47838DF2e6099A8439981f0B717", // Phone
+ "0xAD4bcf258852e9d47E580798d312E1a52D59E721", // Razil
+ "0xAd6D6c80910096b40e45690506a9f1052e072dCB", // Teru
+ "0x309b9235edbe1C6f840816771c6C21aDa6c275EE", // Cowchain
+ "0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3" // Local optiflex
+ ];
public UserService(
IEvmManager evmManager,
diff --git a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs
index 4a7877d..0bb3d16 100644
--- a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs
+++ b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs
@@ -1228,7 +1228,7 @@ public class GmxV2Service
{
var position = await GetGmxPositionsV2(web3, publicAddress);
var positionsPerTicker =
- position.First(p => GmxV2Helpers.SameAddress(p.MarketAddress, GmxV2Helpers.GetMarketAddress(ticker)));
+ position.First(p => GmxV2Helpers.SameAddress(p.Key, publicAddress));
return GmxV2Mappers.Map(positionsPerTicker, ticker);
}
@@ -1375,6 +1375,7 @@ public class GmxV2Service
public async Task QuantityInPosition(Web3 web3, string publicAddress, Enums.Ticker ticker)
{
- return (await GetTrade(web3, publicAddress, ticker)).Status == Enums.TradeStatus.Filled ? 1 : 0;
+ var trade = await GetTrade(web3, publicAddress, ticker);
+ return trade.Status == Enums.TradeStatus.Filled ? trade.Quantity : 0;
}
}
\ No newline at end of file