Finish copy trading

This commit is contained in:
2025-11-20 14:46:54 +07:00
parent ff2df2d9ac
commit 190a9cf12d
13 changed files with 96 additions and 222 deletions

View File

@@ -407,7 +407,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
_copyTradingStreamHandle = await streamProvider.GetStream<Position>(streamId)
.SubscribeAsync(OnCopyTradingPositionReceivedAsync);
_logger.LogInformation("LiveTradingBotGrain {GrainId} subscribed to copy trading stream for master bot {MasterBotId}",
_logger.LogInformation(
"LiveTradingBotGrain {GrainId} subscribed to copy trading stream for master bot {MasterBotId}",
this.GetPrimaryKey(), _state.State.Config.MasterBotIdentifier.Value);
}
catch (Exception ex)
@@ -425,7 +426,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{
await _copyTradingStreamHandle.UnsubscribeAsync();
_copyTradingStreamHandle = null;
_logger.LogInformation("LiveTradingBotGrain {GrainId} unsubscribed from copy trading stream", this.GetPrimaryKey());
_logger.LogInformation("LiveTradingBotGrain {GrainId} unsubscribed from copy trading stream",
this.GetPrimaryKey());
}
}
@@ -438,7 +440,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{
if (_tradingBot == null)
{
_logger.LogWarning("Received copy trading position {PositionId} but trading bot is not running for bot {GrainId}",
_logger.LogWarning(
"Received copy trading position {PositionId} but trading bot is not running for bot {GrainId}",
masterPosition.Identifier, this.GetPrimaryKey());
return;
}
@@ -511,41 +514,52 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
// Check if copy trading authorization is still valid
if (_state.State.Config.IsForCopyTrading && _state.State.Config.MasterBotIdentifier.HasValue)
{
try
// Check if copy trading validation should be bypassed (for testing)
var enableValidation = Environment.GetEnvironmentVariable("ENABLE_COPY_TRADING_VALIDATION")?
.Equals("true", StringComparison.OrdinalIgnoreCase) ?? true;
if (enableValidation)
{
var ownedKeys = await _kaigenService.GetOwnedKeysAsync(_state.State.User);
var masterStrategy = await ServiceScopeHelpers.WithScopedService<IBotService, Bot>(
_scopeFactory,
async botService => await botService.GetBotByIdentifier(_state.State.Config.MasterBotIdentifier.Value));
if (masterStrategy == null)
try
{
_logger.LogWarning("Master strategy {MasterBotId} not found", _state.State.Config.MasterBotIdentifier.Value);
return;
var ownedKeys = await _kaigenService.GetOwnedKeysAsync(_state.State.User);
var masterStrategy = await ServiceScopeHelpers.WithScopedService<IBotService, Bot>(
_scopeFactory,
async botService =>
await botService.GetBotByIdentifier(_state.State.Config.MasterBotIdentifier.Value));
if (masterStrategy == null)
{
_logger.LogWarning("Master strategy {MasterBotId} not found",
_state.State.Config.MasterBotIdentifier.Value);
return;
}
var hasMasterStrategyKey = ownedKeys.Items.Any(key =>
string.Equals(key.AgentName, masterStrategy.User.AgentName,
StringComparison.OrdinalIgnoreCase) &&
key.Owned >= 1);
if (!hasMasterStrategyKey)
{
_logger.LogWarning(
"Copy trading bot {GrainId} no longer has authorization for master strategy {MasterBotId}. Stopping bot.",
this.GetPrimaryKey(), _state.State.Config.MasterBotIdentifier.Value);
await StopAsync(
"Copy trading authorization revoked - user no longer owns keys for master strategy");
return;
}
}
var hasMasterStrategyKey = ownedKeys.Items.Any(key =>
string.Equals(key.AgentName, masterStrategy.User.AgentName, StringComparison.OrdinalIgnoreCase) &&
key.Owned >= 1);
if (!hasMasterStrategyKey)
catch (Exception ex)
{
_logger.LogWarning(
"Copy trading bot {GrainId} no longer has authorization for master strategy {MasterBotId}. Stopping bot.",
_logger.LogError(ex,
"Failed to verify copy trading authorization for bot {GrainId} with master strategy {MasterBotId}. Continuing execution.",
this.GetPrimaryKey(), _state.State.Config.MasterBotIdentifier.Value);
await StopAsync("Copy trading authorization revoked - user no longer owns keys for master strategy");
return;
SentrySdk.CaptureException(ex);
}
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to verify copy trading authorization for bot {GrainId} with master strategy {MasterBotId}. Continuing execution.",
this.GetPrimaryKey(), _state.State.Config.MasterBotIdentifier.Value);
SentrySdk.CaptureException(ex);
}
}
if (_tradingBot.Positions.Any(p => p.Value.IsOpen() || p.Value.Status.Equals(PositionStatus.New)))
@@ -1128,7 +1142,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
_scopeFactory,
async tradingService => await tradingService.GetPositionsByInitiatorIdentifierAsync(botId));
var openPositions = positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ?? new List<Position>();
var openPositions = positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ??
new List<Position>();
if (openPositions.Any())
{
@@ -1140,13 +1155,16 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{
try
{
_logger.LogInformation("Closing position {PositionId} for bot {GrainId}", position.Identifier, botId);
_logger.LogInformation("Closing position {PositionId} for bot {GrainId}", position.Identifier,
botId);
await ClosePositionAsync(position.Identifier);
_logger.LogInformation("Successfully closed position {PositionId} for bot {GrainId}", position.Identifier, botId);
_logger.LogInformation("Successfully closed position {PositionId} for bot {GrainId}",
position.Identifier, botId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to close position {PositionId} for bot {GrainId}", position.Identifier, botId);
_logger.LogError(ex, "Failed to close position {PositionId} for bot {GrainId}",
position.Identifier, botId);
// Continue with other positions even if one fails
}
}

View File

@@ -93,13 +93,16 @@ public class TradingBotBase : ITradingBot
{
case BotStatus.Saved:
var indicatorNames = Config.Scenario.Indicators.Select(i => i.Type.ToString()).ToList();
var modeText = Config.IsForWatchingOnly ? "Watch Only" :
Config.IsForCopyTrading ? "Copy Trading" : "Live Trading";
var startupMessage = $"🚀 Bot Started Successfully\n\n" +
$"📊 Trading Setup:\n" +
$"🎯 Ticker: `{Config.Ticker}`\n" +
$"⏰ Timeframe: `{Config.Timeframe}`\n" +
$"🎮 Scenario: `{Config.Scenario?.Name ?? "Unknown"}`\n" +
$"💰 Balance: `${Config.BotTradingBalance:F2}`\n" +
$"👀 Mode: `{(Config.IsForWatchingOnly ? "Watch Only" : "Live Trading")}`\n\n" +
$"👀 Mode: `{modeText}`\n\n" +
$"📈 Active Indicators: `{string.Join(", ", indicatorNames)}`\n\n" +
$"✅ Ready to monitor signals and execute trades\n" +
$"📢 Notifications will be sent when positions are triggered";

View File

@@ -71,7 +71,9 @@ namespace Managing.Application.ManageBot
try
{
var config = await grain.GetConfiguration();
var account = await grain.GetAccount();
var account = await ServiceScopeHelpers.WithScopedService<IAccountService, Account>(
_scopeFactory,
async accountService => await accountService.GetAccount(config.AccountName, true, false));
await grain.StopAsync("Deleting bot");
await _botRepository.DeleteBot(identifier);
await grain.DeleteAsync();

View File

@@ -8,7 +8,6 @@ using Managing.Domain.Accounts;
using Managing.Domain.Bots;
using Managing.Domain.Users;
using MediatR;
using System;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
@@ -50,7 +49,7 @@ namespace Managing.Application.ManageBot
// Check if copy trading validation should be bypassed (for testing)
var enableValidation = Environment.GetEnvironmentVariable("ENABLE_COPY_TRADING_VALIDATION")?
.Equals("true", StringComparison.OrdinalIgnoreCase) == true;
.Equals("true", StringComparison.OrdinalIgnoreCase) ?? true;
if (enableValidation)
{