Files
managing-apps/src/Managing.Bootstrap/ApiBootstrap.cs
2025-10-03 01:27:47 +07:00

542 lines
25 KiB
C#

using System.Net;
using System.Reflection;
using Discord.Commands;
using Discord.WebSocket;
using FluentValidation;
using Managing.Application;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Accounts;
using Managing.Application.Agents;
using Managing.Application.Backtests;
using Managing.Application.Grains;
using Managing.Application.ManageBot;
using Managing.Application.ManageBot.Commands;
using Managing.Application.MoneyManagements;
using Managing.Application.Orleans;
using Managing.Application.Scenarios;
using Managing.Application.Shared;
using Managing.Application.Shared.Behaviours;
using Managing.Application.Synth;
using Managing.Application.Trading;
using Managing.Application.Trading.Commands;
using Managing.Application.Trading.Handlers;
using Managing.Application.Users;
using Managing.Application.Workers;
using Managing.Domain.Trades;
using Managing.Infrastructure.Databases;
using Managing.Infrastructure.Databases.InfluxDb;
using Managing.Infrastructure.Databases.InfluxDb.Abstractions;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Databases.PostgreSql.Configurations;
using Managing.Infrastructure.Evm;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models.Privy;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Subgraphs;
using Managing.Infrastructure.Exchanges;
using Managing.Infrastructure.Exchanges.Abstractions;
using Managing.Infrastructure.Exchanges.Exchanges;
using Managing.Infrastructure.Messengers.Discord;
using Managing.Infrastructure.Storage;
using MediatR;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Orleans.Configuration;
namespace Managing.Bootstrap;
public static class ApiBootstrap
{
private static readonly Assembly ApplicationAssembly = typeof(StartBotCommand).GetTypeInfo().Assembly;
public static IServiceCollection RegisterApiDependencies(this IServiceCollection services,
IConfiguration configuration)
{
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
return services
.AddApplication()
.AddInfrastructure(configuration)
.AddWorkers(configuration)
.AddFluentValidation()
.AddMediatR()
;
}
public static IServiceCollection AddHostedServices(this IServiceCollection services)
{
services.AddHostedService<GrainInitializer>();
return services;
}
// Note: IClusterClient is automatically available in co-hosting scenarios
// through IGrainFactory. Services should inject IGrainFactory instead of IClusterClient
// to avoid circular dependency issues during DI container construction.
public static IHostBuilder ConfigureOrleans(this IHostBuilder hostBuilder, IConfiguration configuration,
bool isProduction)
{
// Check if Orleans grains should be active using root-level configuration
var runOrleansGrains = configuration.GetValue<bool>("RunOrleansGrains", true);
// Check environment variable as override
var runOrleansGrainsEnv = Environment.GetEnvironmentVariable("RUN_ORLEANS_GRAINS");
if (!string.IsNullOrEmpty(runOrleansGrainsEnv) &&
bool.TryParse(runOrleansGrainsEnv, out var runOrleansGrainsFromEnv))
{
runOrleansGrains = runOrleansGrainsFromEnv;
}
// Allow disabling Orleans clustering entirely in case of issues
var disableOrleansClusteringEnv = Environment.GetEnvironmentVariable("DISABLE_ORLEANS_CLUSTERING");
var disableOrleansClustering = !string.IsNullOrEmpty(disableOrleansClusteringEnv) &&
bool.TryParse(disableOrleansClusteringEnv, out var disabled) && disabled;
// Get TASK_SLOT for multi-instance configuration
var taskSlotEnv = Environment.GetEnvironmentVariable("TASK_SLOT");
var taskSlot = 1; // Default to 1 if not provided
if (!string.IsNullOrEmpty(taskSlotEnv) && int.TryParse(taskSlotEnv, out var parsedTaskSlot))
{
taskSlot = parsedTaskSlot;
}
// Calculate unique ports based on task slot
var siloPort = 11111 + (taskSlot - 1) * 10; // 11111, 11121, 11131, etc.
var gatewayPort = 30000 + (taskSlot - 1) * 10; // 30000, 30010, 30020, etc.
var dashboardPort = 9999 + (taskSlot - 1); // 9999, 10000, 10001, etc.
// Get hostname for clustering - prioritize external IP for multi-server setups
var hostname = Environment.GetEnvironmentVariable("CAPROVER_SERVER_IP") ?? // CapRover server IP
Environment.GetEnvironmentVariable("EXTERNAL_IP") ?? // Custom external IP
Environment.GetEnvironmentVariable("HOSTNAME") ?? // Container hostname
Environment.GetEnvironmentVariable("COMPUTERNAME") ?? // Windows hostname
"localhost";
// For Docker containers, always use localhost for same-server clustering
IPAddress advertisedIP = IPAddress.Loopback; // Advertise as localhost for same-server clustering
// Only use external IP if specifically provided for multi-server scenarios
var externalIP = Environment.GetEnvironmentVariable("CAPROVER_SERVER_IP") ??
Environment.GetEnvironmentVariable("EXTERNAL_IP");
if (!string.IsNullOrEmpty(externalIP) && IPAddress.TryParse(externalIP, out var parsedExternalIP))
{
advertisedIP = parsedExternalIP;
Console.WriteLine($"Using external IP for multi-server clustering: {advertisedIP}");
}
else
{
Console.WriteLine($"Using localhost for same-server clustering: {advertisedIP}");
}
var postgreSqlConnectionString = configuration.GetSection("PostgreSql")["Orleans"];
var siloRole = Environment.GetEnvironmentVariable("SILO_ROLE") ?? "Trading";
Console.WriteLine($"Task Slot: {taskSlot}");
Console.WriteLine($"Hostname: {hostname}");
Console.WriteLine($"Advertised IP: {advertisedIP}");
Console.WriteLine($"Role: {siloRole}");
Console.WriteLine($"Silo port: {siloPort}");
Console.WriteLine($"Gateway port: {gatewayPort}");
Console.WriteLine($"Dashboard port: {dashboardPort}");
return hostBuilder.UseOrleans(siloBuilder =>
{
// Configure clustering with improved networking or use localhost clustering if disabled
if (!disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString))
{
siloBuilder
.UseAdoNetClustering(options =>
{
options.ConnectionString = postgreSqlConnectionString;
options.Invariant = "Npgsql";
})
.Configure<EndpointOptions>(options =>
{
// Advertise the specific IP for clustering
options.AdvertisedIPAddress = advertisedIP;
options.SiloPort = siloPort;
options.GatewayPort = gatewayPort;
options.SiloListeningEndpoint = new IPEndPoint(IPAddress.Any, siloPort);
options.GatewayListeningEndpoint = new IPEndPoint(IPAddress.Any, gatewayPort);
})
.Configure<ClusterOptions>(options =>
{
options.ServiceId = "ManagingApp";
options.ClusterId = configuration["ASPNETCORE_ENVIRONMENT"] ?? "Development";
})
.Configure<SiloOptions>(options =>
{
// Configure silo address for multi-server clustering
options.SiloName = $"ManagingApi-{taskSlot}-{siloRole}";
Console.WriteLine($"Configuring silo with role: {siloRole}");
});
}
else
{
// Fallback to localhost clustering for testing or when database is unavailable
siloBuilder.UseLocalhostClustering(siloPort, gatewayPort);
}
// Conditionally configure reminder service based on flag
if (runOrleansGrains && !disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString))
{
siloBuilder.UseAdoNetReminderService(options =>
{
options.ConnectionString = postgreSqlConnectionString;
options.Invariant = "Npgsql";
});
}
// Configure networking - use specific IP for Docker containerized environments
if (disableOrleansClustering)
{
// Use localhost clustering when clustering is disabled
siloBuilder.ConfigureEndpoints(IPAddress.Loopback, siloPort, gatewayPort);
}
else if (string.IsNullOrEmpty(postgreSqlConnectionString))
{
// In Docker/containerized environments, use endpoint configuration
siloBuilder.Configure<EndpointOptions>(options =>
{
// Advertise the specific IP for clustering
options.AdvertisedIPAddress = advertisedIP;
options.SiloPort = siloPort;
options.GatewayPort = gatewayPort;
options.SiloListeningEndpoint = new IPEndPoint(IPAddress.Any, siloPort);
options.GatewayListeningEndpoint = new IPEndPoint(IPAddress.Any, gatewayPort);
});
}
siloBuilder
.Configure<MessagingOptions>(options =>
{
// Configure messaging for better reliability with increased timeouts
options.ResponseTimeout = TimeSpan.FromSeconds(60);
options.DropExpiredMessages = true;
})
.Configure<ClusterMembershipOptions>(options =>
{
// Configure cluster membership for better resilience
options.EnableIndirectProbes = true;
options.ProbeTimeout = TimeSpan.FromSeconds(10);
options.IAmAliveTablePublishTimeout = TimeSpan.FromSeconds(30);
options.MaxJoinAttemptTime = TimeSpan.FromSeconds(120);
// Improved settings for development environments with stale members
options.DefunctSiloCleanupPeriod = TimeSpan.FromMinutes(1);
options.DefunctSiloExpiration = TimeSpan.FromMinutes(2);
})
.Configure<GatewayOptions>(options =>
{
// Configure gateway with improved timeouts
options.GatewayListRefreshPeriod = TimeSpan.FromSeconds(60);
});
// Conditionally configure grain execution based on flag
if (runOrleansGrains)
{
siloBuilder.Configure<GrainCollectionOptions>(options =>
{
// Enable grain collection for active grains
options.CollectionAge = TimeSpan.FromMinutes(10);
options.CollectionQuantum = TimeSpan.FromMinutes(1);
});
}
else
{
// Disable grain execution completely
siloBuilder.Configure<GrainCollectionOptions>(options =>
{
// Disable grain collection to prevent grains from running
options.CollectionAge = TimeSpan.FromDays(365); // Very long collection age
options.CollectionQuantum = TimeSpan.FromDays(1); // Very long quantum
});
}
siloBuilder
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Information));
// Only enable dashboard in development to avoid shutdown issues
if (!isProduction)
{
siloBuilder.UseDashboard(options =>
{
// Configure dashboard with proper shutdown handling and unique ports per instance
options.Port = dashboardPort;
options.HostSelf = true;
options.CounterUpdateIntervalMs = 10000; // 10 seconds
options.HideTrace = true; // Hide trace to reduce dashboard overhead
options.Host = "0.0.0.0"; // Allow external connections
options.Username = "admin";
options.Password = "admin";
});
}
// Configure grain storage - use ADO.NET for production or memory for fallback
if (!disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString))
{
siloBuilder
.AddAdoNetGrainStorage("bot-store", options =>
{
options.ConnectionString = postgreSqlConnectionString;
options.Invariant = "Npgsql";
})
.AddAdoNetGrainStorage("registry-store", options =>
{
options.ConnectionString = postgreSqlConnectionString;
options.Invariant = "Npgsql";
})
.AddAdoNetGrainStorage("agent-store", options =>
{
options.ConnectionString = postgreSqlConnectionString;
options.Invariant = "Npgsql";
})
.AddAdoNetGrainStorage("platform-summary-store", options =>
{
options.ConnectionString = postgreSqlConnectionString;
options.Invariant = "Npgsql";
})
.AddAdoNetGrainStorage("candle-store", options =>
{
options.ConnectionString = postgreSqlConnectionString;
options.Invariant = "Npgsql";
});
}
else
{
// Fallback to memory storage when database is unavailable
siloBuilder
.AddMemoryGrainStorage("bot-store")
.AddMemoryGrainStorage("registry-store")
.AddMemoryGrainStorage("agent-store")
.AddMemoryGrainStorage("platform-summary-store")
.AddMemoryGrainStorage("candle-store");
}
// Configure Orleans Streams for price data distribution
siloBuilder.AddMemoryStreams("ManagingStreamProvider")
.AddMemoryGrainStorage("PubSubStore");
siloBuilder
.ConfigureServices(services =>
{
// Register custom placement directors for role-based placement
services.AddPlacementDirector<ComputePlacementStrategy, ComputePlacementDirector>();
services.AddPlacementDirector<TradingPlacementStrategy, TradingPlacementDirector>();
// Register existing services for Orleans DI
// These will be available to grains through dependency injection
services.AddTransient<IExchangeService, ExchangeService>();
services.AddTransient<IAccountService, AccountService>();
services.AddTransient<ITradingService, TradingService>();
services.AddTransient<IMessengerService, MessengerService>();
// Use Singleton for InfluxDB repositories to prevent connection disposal issues in Orleans grains
services.AddSingleton<IInfluxDbRepository, InfluxDbRepository>();
services.AddSingleton<ICandleRepository, CandleRepository>();
services.AddSingleton<IAgentBalanceRepository, AgentBalanceRepository>();
});
})
;
}
private static IServiceCollection AddApplication(this IServiceCollection services)
{
services.AddScoped<ITradingService, TradingService>();
services.AddScoped<IScenarioService, ScenarioService>();
services.AddScoped<IMoneyManagementService, MoneyManagementService>();
services.AddScoped<IAccountService, AccountService>();
services.AddScoped<IStatisticService, StatisticService>();
services.AddScoped<IAgentService, AgentService>();
services.AddScoped<ISettingsService, SettingsService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IGeneticService, GeneticService>();
services.AddScoped<IBotService, BotService>();
services.AddScoped<IWorkerService, WorkerService>();
services.AddScoped<ISynthPredictionService, SynthPredictionService>();
services.AddScoped<ISynthApiClient, SynthApiClient>();
services.AddScoped<IPricesService, PricesService>();
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
// Processors
services.AddTransient<IBacktester, Backtester>();
services.AddTransient<IExchangeProcessor, EvmProcessor>();
services.AddTransient<ITradaoService, TradaoService>();
services.AddTransient<IExchangeService, ExchangeService>();
services.AddTransient<IPrivyService, PrivyService>();
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
services.AddTransient<IWebhookService, WebhookService>();
services.AddTransient<IKaigenService, KaigenService>();
services.AddSingleton<IMessengerService, MessengerService>();
services.AddSingleton<IDiscordService, DiscordService>();
// Admin services
services.AddSingleton<IAdminConfigurationService, AdminConfigurationService>();
return services;
}
private static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
// Database
services.AddSingleton<IPostgreSqlSettings>(sp =>
sp.GetRequiredService<IOptions<PostgreSqlSettings>>().Value);
services.AddSingleton<IInfluxDbSettings>(sp =>
sp.GetRequiredService<IOptions<InfluxDbSettings>>().Value);
services.AddSingleton<IPrivySettings>(sp =>
sp.GetRequiredService<IOptions<PrivySettings>>().Value);
services.Configure<KaigenSettings>(configuration.GetSection("Kaigen"));
// Evm
services.AddGbcFeed();
services.AddUniswapV2();
services.AddChainlink();
services.AddChainlinkGmx();
services.AddSingleton<IEvmManager, EvmManager>();
// PostgreSql Repositories
services.AddTransient<IAccountRepository, PostgreSqlAccountRepository>();
services.AddTransient<IBacktestRepository, PostgreSqlBacktestRepository>();
services.AddTransient<IGeneticRepository, PostgreSqlGeneticRepository>();
services.AddTransient<ITradingRepository, PostgreSqlTradingRepository>();
services.AddTransient<ISettingsRepository, PostgreSqlSettingsRepository>();
services.AddTransient<IUserRepository, PostgreSqlUserRepository>();
services.AddTransient<IAgentSummaryRepository, AgentSummaryRepository>();
services.AddTransient<IStatisticRepository, PostgreSqlStatisticRepository>();
services.AddTransient<IBotRepository, PostgreSqlBotRepository>();
services.AddTransient<IWorkerRepository, PostgreSqlWorkerRepository>();
services.AddTransient<ISynthRepository, PostgreSqlSynthRepository>();
// InfluxDb Repositories - Use Singleton for proper connection management in Orleans grains
services.AddSingleton<IInfluxDbRepository, InfluxDbRepository>();
services.AddSingleton<ICandleRepository, CandleRepository>();
services.AddSingleton<IAgentBalanceRepository, AgentBalanceRepository>();
// Cache
services.AddDistributedMemoryCache();
services.AddTransient<ICacheService, CacheService>();
services.AddSingleton<ITaskCache, TaskCache>();
// Services
services.AddTransient<ICacheService, CacheService>();
services.AddSingleton<ITaskCache, TaskCache>();
return services;
}
private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration)
{
// Balance Workers
if (configuration.GetValue<bool>("WorkerBalancesTracking", false))
{
services.AddHostedService<BalanceTrackingWorker>();
}
if (configuration.GetValue<bool>("WorkerNotifyBundleBacktest", false))
{
services.AddHostedService<NotifyBundleBacktestWorker>();
}
// Price Workers
if (configuration.GetValue<bool>("WorkerPricesFifteenMinutes", false))
{
services.AddHostedService<PricesFifteenMinutesWorker>();
}
if (configuration.GetValue<bool>("WorkerPricesOneHour", false))
{
services.AddHostedService<PricesOneHourWorker>();
}
if (configuration.GetValue<bool>("WorkerPricesFourHours", false))
{
services.AddHostedService<PricesFourHoursWorker>();
}
if (configuration.GetValue<bool>("WorkerPricesOneDay", false))
{
services.AddHostedService<PricesOneDayWorker>();
}
if (configuration.GetValue<bool>("WorkerPricesFiveMinutes", false))
{
services.AddHostedService<PricesFiveMinutesWorker>();
}
// Other Workers
if (configuration.GetValue<bool>("WorkerSpotlight", false))
{
services.AddHostedService<SpotlightWorker>();
}
if (configuration.GetValue<bool>("WorkerTraderWatcher", false))
{
services.AddHostedService<TraderWatcher>();
}
if (configuration.GetValue<bool>("WorkerLeaderboard", false))
{
services.AddHostedService<LeaderboardWorker>();
}
if (configuration.GetValue<bool>("WorkerFundingRatesWatcher", false))
{
services.AddHostedService<FundingRatesWatcher>();
}
if (configuration.GetValue<bool>("WorkerGeneticAlgorithm", false))
{
services.AddHostedService<GeneticAlgorithmWorker>();
}
// DEPRECATED: BundleBacktestWorker has been replaced by BundleBacktestGrain
// Bundle backtest processing is now handled by Orleans grain triggered directly from Backtester.cs
// if (configuration.GetValue<bool>("WorkerBundleBacktest", false))
// {
// services.AddHostedService<BundleBacktestWorker>();
// }
return services;
}
private static IServiceCollection AddMediatR(this IServiceCollection services)
{
return services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()))
.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>))
.AddTransient(typeof(IUnhandledExceptionBehaviour<,>), typeof(UnhandledExceptionBehaviour<,>));
}
private static IServiceCollection AddFluentValidation(this IServiceCollection services)
{
return services.AddValidatorsFromAssembly(ApplicationAssembly);
}
public static IWebHostBuilder SetupDiscordBot(this IWebHostBuilder builder)
{
return builder.ConfigureServices(services =>
{
services
.AddSingleton<DiscordSettings>()
.AddSingleton<DiscordSocketClient>()
.AddSingleton<CommandService>()
.AddHostedService<DiscordService>();
});
}
}