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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user