Files
managing-apps/src/Managing.Bootstrap/ApiBootstrap.cs
cryptooda 7108907e0e Add Redis support for SignalR backplane and caching
- Introduced Redis configuration in appsettings.json to enable SignalR backplane functionality.
- Updated Program.cs to conditionally configure SignalR with Redis if a connection string is provided.
- Added Redis connection service registration in ApiBootstrap for distributed scenarios.
- Included necessary package references for StackExchange.Redis and Microsoft.Extensions.Caching.StackExchangeRedis in project files.
- Implemented password masking for Redis connection strings to enhance security.
2026-01-07 16:59:10 +07:00

655 lines
31 KiB
C#
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Net;
using System.Reflection;
using Discord.Commands;
using Discord.WebSocket;
using Flagsmith;
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.Whitelist;
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.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}");
})
.Configure<MessagingOptions>(options =>
{
// Increase timeout for grain deactivation during shutdown
options.ResponseTimeout = TimeSpan.FromSeconds(30);
})
.Configure<GrainCollectionOptions>(options =>
{
// Configure grain collection timeouts
options.CollectionAge = TimeSpan.FromMinutes(10);
});
}
else
{
// Fallback to localhost clustering for testing or when database is unavailable
siloBuilder.UseLocalhostClustering(siloPort, gatewayPort)
.Configure<MessagingOptions>(options =>
{
// Increase timeout for grain deactivation during shutdown
options.ResponseTimeout = TimeSpan.FromSeconds(30);
})
.Configure<GrainCollectionOptions>(options =>
{
// Configure grain collection timeouts
options.CollectionAge = TimeSpan.FromMinutes(10);
});
}
// 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
// Set to 2 hours to support long-running backtests that can take 47+ minutes
options.ResponseTimeout = TimeSpan.FromHours(2);
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
// Set to 2.5 hours to allow long-running backtests (up to 2 hours) to complete
// without being collected prematurely
options.CollectionAge = TimeSpan.FromMinutes(150); // 2.5 hours
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);
// Reduce verbosity of Orleans status update messages for long-running operations
// These are informational and can be very verbose during long backtests
logging.AddFilter("Orleans.Runtime.Messaging.IncomingMessageAcceptor", LogLevel.Warning);
logging.AddFilter("Orleans.Runtime.Messaging.MessageCenter", LogLevel.Warning);
// Keep important Orleans logs but reduce status update noise
logging.AddFilter("Microsoft.Orleans.Runtime.Messaging", LogLevel.Warning);
});
// 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.AddScoped<IFlagsmithService, FlagsmithService>();
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
services.AddTransient<ICommandHandler<OpenSpotPositionRequest, Position>, OpenSpotPositionCommandHandler>();
services.AddTransient<ICommandHandler<CloseBacktestFuturesPositionCommand, Position>, CloseBacktestFuturesPositionCommandHandler>();
services.AddTransient<ICommandHandler<CloseSpotPositionCommand, Position>, CloseSpotPositionCommandHandler>();
services.AddTransient<ICommandHandler<CloseFuturesPositionCommand, Position>, CloseFuturesPositionCommandHandler>();
// Keep old handler for backward compatibility
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
// Processors
services.AddTransient<IBacktester, Backtester>();
services.AddTransient<JobService>();
services.AddTransient<IExchangeProcessor, EvmProcessor>();
services.AddTransient<ITradaoService, TradaoService>();
services.AddTransient<IExchangeService, ExchangeService>();
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
services.AddHttpClient<IWebhookService, WebhookService>();
services.AddTransient<IKaigenService, KaigenService>();
services.AddTransient<IWhitelistService, WhitelistService>();
services.AddSingleton<IMessengerService, MessengerService>();
services.AddSingleton<IDiscordService, DiscordService>();
// Admin services
services.AddSingleton<IAdminConfigurationService, AdminConfigurationService>();
// LLM and MCP services
services.AddScoped<ILlmService, Managing.Application.LLM.LlmService>();
services.AddScoped<IMcpService, Managing.Mcp.McpService>();
// MCP Tools (underlying implementations)
services.AddScoped<Managing.Mcp.Tools.BacktestTools>();
services.AddScoped<Managing.Mcp.Tools.DataTools>();
services.AddScoped<Managing.Mcp.Tools.BotTools>();
services.AddScoped<Managing.Mcp.Tools.IndicatorTools>();
// MCP Tool Wrappers (with tool definitions)
services.AddScoped<Managing.Mcp.McpTools.BacktestMcpTools>();
services.AddScoped<Managing.Mcp.McpTools.DataMcpTools>();
services.AddScoped<Managing.Mcp.McpTools.BotMcpTools>();
services.AddScoped<Managing.Mcp.McpTools.IndicatorMcpTools>();
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.Configure<KaigenSettings>(configuration.GetSection("Kaigen"));
services.Configure<FlagsmithSettings>(configuration.GetSection("Flagsmith"));
// Flagsmith - Register client as Singleton
services.AddSingleton<FlagsmithClient>(sp =>
{
var settings = sp.GetRequiredService<IOptions<FlagsmithSettings>>().Value;
if (string.IsNullOrWhiteSpace(settings.ApiKey))
{
throw new InvalidOperationException("Flagsmith ApiKey is not configured. Please set the Flagsmith:ApiKey configuration value.");
}
if (string.IsNullOrWhiteSpace(settings.ApiUrl))
{
throw new InvalidOperationException("Flagsmith ApiUrl is not configured. Please set the Flagsmith:ApiUrl configuration value.");
}
return new FlagsmithClient(settings.ApiKey, settings.ApiUrl);
});
// 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<IJobRepository, PostgreSqlJobRepository>();
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>();
services.AddTransient<IWhitelistRepository, PostgreSqlWhitelistRepository>();
// 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>();
// Redis (for SignalR backplane and other distributed scenarios)
services.AddRedis(configuration);
return services;
}
private static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration)
{
// Check if Redis is configured
// Priority: ConnectionStrings:Redis (can be set via ConnectionStrings__Redis env var) > REDIS_URL env var
var redisConnectionString = configuration.GetConnectionString("Redis")
?? configuration["REDIS_URL"];
if (!string.IsNullOrWhiteSpace(redisConnectionString))
{
Console.WriteLine($"✅ Redis configured: {MaskRedisPassword(redisConnectionString)}");
// Register generic Redis connection service for various use cases
// (SignalR backplane, distributed caching, pub/sub, etc.)
services.AddSingleton<IRedisConnectionService, RedisConnectionService>();
}
else
{
Console.WriteLine(" Redis not configured - running in single-instance mode");
// Register a no-op Redis service that returns null connections
services.AddSingleton<IRedisConnectionService>(sp =>
new RedisConnectionService(configuration, sp.GetRequiredService<ILogger<RedisConnectionService>>()));
}
return services;
}
private static string MaskRedisPassword(string connectionString)
{
if (connectionString.Contains("password=", StringComparison.OrdinalIgnoreCase))
{
var parts = connectionString.Split(',');
for (int i = 0; i < parts.Length; i++)
{
if (parts[i].Trim().StartsWith("password=", StringComparison.OrdinalIgnoreCase))
{
parts[i] = "password=***";
}
}
return string.Join(",", parts);
}
return connectionString;
}
private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration)
{
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>();
});
}
}