Add copy trading functionality with StartCopyTrading endpoint and related models. Implemented position copying from master bot and subscription to copy trading stream in LiveTradingBotGrain. Updated TradingBotConfig to support copy trading parameters.

This commit is contained in:
2025-11-16 14:54:17 +07:00
parent 428e36d744
commit 1e15d5f23b
10 changed files with 711 additions and 173 deletions

View File

@@ -20,6 +20,7 @@ using Managing.Domain.Trades;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Orleans.Streams;
using static Managing.Common.Enums;
namespace Managing.Application.Bots;
@@ -28,6 +29,7 @@ public class TradingBotBase : ITradingBot
{
public readonly ILogger<TradingBotBase> Logger;
private readonly IServiceScopeFactory _scopeFactory;
private readonly IStreamProvider? _streamProvider;
private const int NEW_POSITION_GRACE_SECONDS = 45; // grace window before evaluating missing orders
private const int
@@ -48,10 +50,12 @@ public class TradingBotBase : ITradingBot
public TradingBotBase(
ILogger<TradingBotBase> logger,
IServiceScopeFactory scopeFactory,
TradingBotConfig config
TradingBotConfig config,
IStreamProvider? streamProvider = null
)
{
_scopeFactory = scopeFactory;
_streamProvider = streamProvider;
Logger = logger;
Config = config;
Signals = new Dictionary<string, LightSignal>();
@@ -228,8 +232,12 @@ public class TradingBotBase : ITradingBot
// Update signals for live trading only
if (!Config.IsForBacktest)
{
await UpdateSignals();
await LoadLastCandle();
if (!Config.IsForCopyTrading)
{
await UpdateSignals();
}
}
if (!Config.IsForWatchingOnly)
@@ -1139,6 +1147,9 @@ public class TradingBotBase : ITradingBot
}
await LogDebug($"✅ Position requested successfully for signal: `{signal.Identifier}`");
await SendPositionToCopyTrading(position);
return position;
}
else
@@ -1175,6 +1186,83 @@ public class TradingBotBase : ITradingBot
}
}
private async Task SendPositionToCopyTrading(Position position)
{
try
{
// Only send to copy trading stream if this is not a copy trading bot itself
if (Config.IsForCopyTrading || _streamProvider == null)
{
return;
}
// Create stream keyed by this bot's identifier for copy trading bots to subscribe to
var streamId = StreamId.Create("CopyTrading", Identifier);
var stream = _streamProvider.GetStream<Position>(streamId);
// Publish the position to the stream
await stream.OnNextAsync(position);
await LogDebug($"📡 Position {position.Identifier} sent to copy trading stream for bot {Identifier}");
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to send position {PositionId} to copy trading stream for bot {BotId}",
position.Identifier, Identifier);
}
}
/// <summary>
/// Creates a copy of a position from a master bot for copy trading
/// </summary>
public async Task CopyPositionFromMasterAsync(Position masterPosition)
{
try
{
// Create a copy signal based on the master position using the proper constructor
var copySignal = new LightSignal(
ticker: Config.Ticker,
direction: masterPosition.OriginDirection,
confidence: Confidence.Medium, // Default confidence for copy trading
candle: LastCandle ?? new Candle
{
Ticker = Config.Ticker,
Timeframe = Config.Timeframe,
Date = DateTime.UtcNow,
Open = masterPosition.Open.Price,
Close = masterPosition.Open.Price,
High = masterPosition.Open.Price,
Low = masterPosition.Open.Price,
Volume = 0
},
date: masterPosition.Open.Date,
exchange: TradingExchanges.GmxV2, // Default exchange
indicatorType: IndicatorType.Composite,
signalType: SignalType.Signal,
indicatorName: "CopyTrading"
);
// Override the identifier to include master position info
copySignal.Identifier = $"copy-{masterPosition.SignalIdentifier}-{Guid.NewGuid()}";
// Store the signal
Signals[copySignal.Identifier] = copySignal;
await LogInformation($"📋 Copy trading: Created copy signal {copySignal.Identifier} for master position {masterPosition.Identifier}");
// Attempt to open the position using the existing OpenPosition method
// This will handle all the position creation logic properly
await OpenPosition(copySignal);
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to copy position {MasterPositionId} for bot {BotId}",
masterPosition.Identifier, Identifier);
throw;
}
}
private async Task<bool> CanOpenPosition(LightSignal signal)
{
// Early return if we're in backtest mode and haven't executed yet