Files
managing-apps/Plan.md
Oda 3de8b5e00e Orlean (#32)
* Start building with orlean

* Add missing file

* Serialize grain state

* Remove grain and proxies

* update and add plan

* Update a bit

* Fix backtest grain

* Fix backtest grain

* Clean a bit
2025-07-30 16:03:30 +07:00

8.9 KiB

Orleans Migration Plan for Managing Apps Trading Bot

Overview

Migrate the TradingBot class to Microsoft Orleans grains for improved performance, scalability, and high availability while maintaining backward compatibility with the Backtester class.

Current Architecture Analysis

TradingBot Key Characteristics

  • Long-running stateful service with complex state (positions, signals, candles, indicators)
  • Timer-based execution via InitWorker(Run)
  • Dependency injection via IServiceScopeFactory
  • Persistence via SaveBackup() and LoadBackup()
  • SignalR integration for real-time updates

Backtester Requirements

  • Creates TradingBot instances as regular classes (line 198: _botFactory.CreateBacktestTradingBot)
  • Runs synchronous backtesting without Orleans overhead
  • Needs direct object manipulation for performance

1. Orleans Grain Design

A. Create ITradingBotGrain Interface

// src/Managing.Application.Abstractions/Grains/ITradingBotGrain.cs
public interface ITradingBotGrain : IGrainWithStringKey
{
    Task StartAsync();
    Task StopAsync();
    Task<BotStatus> GetStatusAsync();
    Task<TradingBotConfig> GetConfigurationAsync();
    Task<bool> UpdateConfigurationAsync(TradingBotConfig newConfig);
    Task<Position> OpenPositionManuallyAsync(TradeDirection direction);
    Task ToggleIsForWatchOnlyAsync();
    Task<TradingBotResponse> GetBotDataAsync();
    Task LoadBackupAsync(BotBackup backup);
}

B. Modify TradingBot Class

// src/Managing.Application/Bots/TradingBot.cs
public class TradingBot : Grain, ITradingBotGrain, ITradingBot
{
    // Keep existing implementation but add Orleans-specific methods
    // Add grain lifecycle management
    // Replace IServiceScopeFactory with Orleans DI
}

2. Program.cs Orleans Configuration

Add to src/Managing.Api/Program.cs after line 188:

// Orleans Configuration
builder.Host.UseOrleans(siloBuilder =>
{
    siloBuilder
        .UseLocalhostClustering() // For local development
        .ConfigureLogging(logging => logging.AddConsole())
        .UseDashboard(options => { options.Port = 8080; })
        .AddMemoryGrainStorageAsDefault()
        .ConfigureServices(services =>
        {
            // Register existing services for Orleans DI
            services.AddSingleton<IExchangeService, ExchangeService>();
            services.AddSingleton<IAccountService, AccountService>();
            services.AddSingleton<ITradingService, TradingService>();
            services.AddSingleton<IMessengerService, MessengerService>();
            services.AddSingleton<IBackupBotService, BackupBotService>();
        });

    // Production clustering configuration
    if (builder.Environment.IsProduction())
    {
        siloBuilder
            .UseAdoNetClustering(options =>
            {
                options.ConnectionString = postgreSqlConnectionString;
                options.Invariant = "Npgsql";
            })
            .UseAdoNetReminderService(options =>
            {
                options.ConnectionString = postgreSqlConnectionString;
                options.Invariant = "Npgsql";
            });
    }
});

// Orleans Client Configuration (for accessing grains from controllers)
builder.Services.AddOrleansClient(clientBuilder =>
{
    clientBuilder.UseLocalhostClustering();
    
    if (builder.Environment.IsProduction())
    {
        clientBuilder.UseAdoNetClustering(options =>
        {
            options.ConnectionString = postgreSqlConnectionString;
            options.Invariant = "Npgsql";
        });
    }
});

3. Conditional Bot Instantiation Strategy

A. Enhanced BotFactory Pattern

// src/Managing.Application/Bots/Base/BotFactory.cs
public class BotFactory : IBotFactory
{
    private readonly IClusterClient _clusterClient;
    private readonly IServiceProvider _serviceProvider;

    public async Task<ITradingBot> CreateTradingBotAsync(TradingBotConfig config, bool useGrain = true)
    {
        if (config.IsForBacktest || !useGrain)
        {
            // For backtesting: Create regular class instance
            return new TradingBot(
                _serviceProvider.GetService<ILogger<TradingBot>>(),
                _serviceProvider.GetService<IServiceScopeFactory>(),
                config
            );
        }
        else
        {
            // For live trading: Use Orleans grain
            var grain = _clusterClient.GetGrain<ITradingBotGrain>(config.Name);
            return new TradingBotGrainProxy(grain, config);
        }
    }
}

B. TradingBotGrainProxy (Adapter Pattern)

// src/Managing.Application/Bots/TradingBotGrainProxy.cs
public class TradingBotGrainProxy : ITradingBot
{
    private readonly ITradingBotGrain _grain;
    private TradingBotConfig _config;

    public TradingBotGrainProxy(ITradingBotGrain grain, TradingBotConfig config)
    {
        _grain = grain;
        _config = config;
    }

    public async Task Start() => await _grain.StartAsync();
    public async Task Stop() => await _grain.StopAsync();
    
    // Implement all ITradingBot methods by delegating to grain
    // This maintains compatibility with existing bot management code
}

C. Backtester Compatibility

In Backtester.cs (line 198), the factory call remains unchanged:

// This will automatically create a regular class instance due to IsForBacktest = true
var tradingBot = await _botFactory.CreateBacktestTradingBot(config);

4. Orleans Grain State Management

// src/Managing.Application/Bots/TradingBotGrainState.cs
[GenerateSerializer]
public class TradingBotGrainState
{
    [Id(0)] public TradingBotConfig Config { get; set; }
    [Id(1)] public HashSet<LightSignal> Signals { get; set; }
    [Id(2)] public List<Position> Positions { get; set; }
    [Id(3)] public Dictionary<DateTime, decimal> WalletBalances { get; set; }
    [Id(4)] public BotStatus Status { get; set; }
    [Id(5)] public DateTime StartupTime { get; set; }
    [Id(6)] public DateTime CreateDate { get; set; }
}

// Updated TradingBot grain
public class TradingBot : Grain<TradingBotGrainState>, ITradingBotGrain
{
    private IDisposable _timer;

    public override async Task OnActivateAsync(CancellationToken cancellationToken)
    {
        // Initialize grain state and start timer-based execution
        if (State.Config != null && State.Status == BotStatus.Running)
        {
            await StartTimerAsync();
        }
    }

    private async Task StartTimerAsync()
    {
        var interval = CandleExtensions.GetIntervalFromTimeframe(State.Config.Timeframe);
        _timer = RegisterTimer(async _ => await Run(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(interval));
    }
}

5. Implementation Roadmap

Phase 1: Infrastructure Setup

  1. Add Orleans packages (already done in Managing.Api.csproj)
  2. Configure Orleans in Program.cs with clustering and persistence
  3. Create grain interfaces and state classes

Phase 2: Core Migration

  1. Create ITradingBotGrain interface with async methods
  2. Modify TradingBot class to inherit from Grain<TradingBotGrainState>
  3. Implement TradingBotGrainProxy for compatibility
  4. Update BotFactory with conditional instantiation logic

Phase 3: Service Integration

  1. Replace IServiceScopeFactory with Orleans dependency injection
  2. Update timer management to use Orleans grain timers
  3. Migrate state persistence from SaveBackup/LoadBackup to Orleans state
  4. Update bot management services to work with grains

Phase 4: Testing & Optimization

  1. Test backtesting compatibility (should remain unchanged)
  2. Performance testing with multiple concurrent bots
  3. High availability testing with node failures
  4. Memory and resource optimization

Key Benefits

  1. High Availability: Orleans automatic failover and grain migration
  2. Scalability: Distributed bot execution across multiple nodes
  3. Performance: Reduced serialization overhead, efficient state management
  4. Backward Compatibility: Backtester continues using regular classes
  5. Simplified State Management: Orleans handles persistence automatically

Migration Considerations

  1. Async Conversion: All bot operations become async
  2. State Serialization: Ensure all state classes are serializable
  3. Timer Management: Replace custom timers with Orleans grain timers
  4. Dependency Injection: Adapt from ASP.NET Core DI to Orleans DI
  5. SignalR Integration: Update to work with distributed grains

Current Status

  • Orleans package added to Managing.Api.csproj
  • Orleans configuration implemented in Program.cs
  • ITradingBotGrain interface created
  • TradingBotGrainState class created
  • TradingBotGrain implementation completed
  • TradingBotResponse model created
  • TradingBotProxy adapter pattern implemented
  • Original TradingBot class preserved for backtesting
  • BotService conditional logic implemented for all creation methods
  • Testing Orleans integration