- appsettings.json: Database:Provider = "MySql" (voller Umstieg). - ConfigMigrator.VerifyMySql + CLI --verify-mysql: liest Accounts, Settings, Trader, Markets aus MySQL zurueck. - Verifiziert: 3 Accounts, 3 Settings, 32 Trader (23 aktiv), 2344 Markets; Werte/AssignedAccountIds stimmen mit Exporten ueberein. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
298 lines
11 KiB
C#
298 lines
11 KiB
C#
using System;
|
|
using MongoDB.Driver;
|
|
using PolyTraderSharp.Extensions;
|
|
using System.Net.Http;
|
|
using System.Threading.Channels;
|
|
using System.Windows.Forms;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Options;
|
|
using PolyTrader.Core.Configuration;
|
|
using PolyTrader.Core.DependencyInjection;
|
|
using PolyTrader.Core.Streaming;
|
|
using PolyTrader.Core.Modularity;
|
|
using PolyTrader.Modules.CopyTrading;
|
|
using PolyTrader.Modules.CopyTrading.Persistence;
|
|
using PolyTraderSharp.Models;
|
|
using PolyTraderSharp.Services;
|
|
|
|
namespace PolyTraderSharp;
|
|
|
|
internal static class Program
|
|
{
|
|
public static IHost? AppHost { get; private set; }
|
|
|
|
[STAThread]
|
|
private static void Main(string[] args)
|
|
{
|
|
// Einmalige Config-Migration Mongo -> MySQL (Phase 6, Stufe 4). Kein UI-Start.
|
|
if (args.Length > 0 && string.Equals(args[0], "--migrate-config", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
RunConfigMigration();
|
|
return;
|
|
}
|
|
|
|
// Config-Migration aus mongoexport-JSON (Ordner, Default "MongoDB") -> MySQL.
|
|
if (args.Length > 0 && string.Equals(args[0], "--migrate-json", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var folder = args.Length > 1 ? args[1] : "MongoDB";
|
|
RunConfigMigrationFromJson(folder);
|
|
return;
|
|
}
|
|
|
|
// Readback-Verifikation der migrierten Config aus MySQL. Kein UI-Start.
|
|
if (args.Length > 0 && string.Equals(args[0], "--verify-mysql", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
RunVerifyMySql();
|
|
return;
|
|
}
|
|
|
|
ApplicationConfiguration.Initialize();
|
|
|
|
var modules = new System.Collections.Generic.List<IPolyTraderModule>
|
|
{
|
|
new CopyTradingModule()
|
|
};
|
|
|
|
AppHost = Host.CreateDefaultBuilder().ConfigureServices(delegate(HostBuilderContext context, IServiceCollection services)
|
|
{
|
|
var databaseOptions = new DatabaseOptions
|
|
{
|
|
Provider = context.Configuration["Database:Provider"] ?? "Mongo",
|
|
MySqlConnectionString = context.Configuration["Database:MySqlConnectionString"] ?? string.Empty,
|
|
ConnectionString = context.Configuration["Database:ConnectionString"] ?? "mongodb://localhost:27017",
|
|
DatabaseName = context.Configuration["Database:DatabaseName"] ?? "PolyTraderDB"
|
|
};
|
|
services.Configure<DatabaseOptions>(context.Configuration.GetSection(DatabaseOptions.SectionName));
|
|
services.AddSingleton<IMongoDatabase>((IServiceProvider sp) =>
|
|
{
|
|
var dbOptions = sp.GetRequiredService<IOptions<DatabaseOptions>>().Value;
|
|
var client = new MongoClient(dbOptions.ConnectionString);
|
|
return client.GetDatabase(dbOptions.DatabaseName);
|
|
});
|
|
services.AddCorePersistence(databaseOptions);
|
|
services.AddSingleton<IBlockchainWssClientFactory, AlchemyWssClientFactory>();
|
|
services.AddSingleton((IServiceProvider sp) => ServerSettings.Load("server_settings.xml"));
|
|
services.AddSingleton<TradingState>();
|
|
services.AddSingleton(delegate(IServiceProvider sp)
|
|
{
|
|
TerminalLogger requiredService2 = sp.GetRequiredService<TerminalLogger>();
|
|
var httpHandler = new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(2), MaxConnectionsPerServer = 100 };
|
|
return new PolymarketApiService(requiredService2, new HttpClient(httpHandler)
|
|
{
|
|
DefaultRequestHeaders =
|
|
{
|
|
{ "User-Agent", "py_clob_client" },
|
|
{ "Accept", "*/*" }
|
|
}
|
|
});
|
|
});
|
|
services.AddSingleton(delegate(IServiceProvider sp)
|
|
{
|
|
TerminalLogger requiredService2 = sp.GetRequiredService<TerminalLogger>();
|
|
var httpHandler = new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(2), MaxConnectionsPerServer = 100 };
|
|
return new PolymarketClobClient(requiredService2, new HttpClient(httpHandler)
|
|
{
|
|
DefaultRequestHeaders =
|
|
{
|
|
{ "User-Agent", "py_clob_client" },
|
|
{ "Accept", "*/*" }
|
|
}
|
|
});
|
|
});
|
|
services.AddSingleton<TerminalLogger>();
|
|
services.AddSingleton<MullvadVpnService>();
|
|
services.AddSingleton<ThreemaService>();
|
|
services.AddSingleton<JobManager>();
|
|
|
|
// MUSS als erster HostedService laufen: hydriert den State, bevor die
|
|
// Trading-Services gegen einen leeren State anlaufen.
|
|
services.AddHostedService<StartupHydrationService>();
|
|
|
|
// Module registrieren ihre eigenen Services/Channels/State selbst.
|
|
foreach (var module in modules)
|
|
{
|
|
services.AddSingleton(module);
|
|
module.RegisterServices(services, context.Configuration);
|
|
}
|
|
|
|
services.AddHostedService<AlchemyWebsocketService>();
|
|
services.AddHostedService<PersistenceService>();
|
|
services.AddHostedService<MarketSyncService>();
|
|
services.AddHostedService<MasterTraderAnalyticsJob>();
|
|
services.AddHostedService<PolymarketWssClient>();
|
|
services.AddHostedService((IServiceProvider sp) => sp.GetRequiredService<MullvadVpnService>());
|
|
services.AddHostedService((IServiceProvider sp) => sp.GetRequiredService<ThreemaService>());
|
|
services.AddTransient<frm_main>();
|
|
services.AddSingleton<PolyTraderSharp.Ui.ShellUiHost>();
|
|
services.AddTransient<PolyTraderSharp.Ui.LauncherForm>();
|
|
}).Build();
|
|
|
|
try
|
|
{
|
|
var db = AppHost.Services.GetRequiredService<IMongoDatabase>();
|
|
|
|
// Einmal-Bereinigung (Alt-Bug): entferne closed_trades mit ObjectId-_id.
|
|
// Das _id muss die int-TradeId sein; fehlerhafte Auto-ObjectIds werden verworfen.
|
|
// Läuft jetzt über die konfigurierte DB statt über einen hart codierten Client.
|
|
try
|
|
{
|
|
var cleanupCol = db.GetCollection<MongoDB.Bson.BsonDocument>("closed_trades");
|
|
cleanupCol.DeleteMany(Builders<MongoDB.Bson.BsonDocument>.Filter.Type("_id", MongoDB.Bson.BsonType.ObjectId));
|
|
}
|
|
catch { }
|
|
|
|
var copyState = AppHost.Services.GetRequiredService<CopyTradingState>();
|
|
var maxTradeDoc = db.GetCollection<MongoDB.Bson.BsonDocument>("closed_trades")
|
|
.Find(Builders<MongoDB.Bson.BsonDocument>.Filter.Empty)
|
|
.SortByDescending(d => d["_id"])
|
|
.Limit(1)
|
|
.FirstOrDefault();
|
|
|
|
if (maxTradeDoc != null && maxTradeDoc.Contains("_id"))
|
|
{
|
|
copyState.TotalCopyTrades = maxTradeDoc["_id"].AsInt32;
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
AppHost.Start();
|
|
|
|
// Shell-Views registrieren (Core-App-Views; Module folgen via module.RegisterUi).
|
|
var uiHost = AppHost.Services.GetRequiredService<PolyTraderSharp.Ui.ShellUiHost>();
|
|
var viewServices = AppHost.Services;
|
|
uiHost.RegisterView(new PolyTrader.Core.Modularity.ModuleView
|
|
{
|
|
Id = "core.terminal",
|
|
Title = "Terminal",
|
|
Group = "Core",
|
|
Order = 10,
|
|
CreateForm = () =>
|
|
{
|
|
var view = new PolyTraderSharp.Ui.Views.TerminalView();
|
|
view.Initialize(viewServices.GetRequiredService<TerminalLogger>());
|
|
return view;
|
|
}
|
|
});
|
|
uiHost.RegisterView(new PolyTrader.Core.Modularity.ModuleView
|
|
{
|
|
Id = "core.settings",
|
|
Title = "Server Settings",
|
|
Group = "Core",
|
|
Order = 20,
|
|
CreateForm = () =>
|
|
{
|
|
var view = new PolyTraderSharp.Ui.Views.SettingsView();
|
|
view.Initialize(
|
|
viewServices.GetRequiredService<ThreemaService>(),
|
|
viewServices.GetRequiredService<MullvadVpnService>(),
|
|
viewServices.GetRequiredService<TerminalLogger>(),
|
|
viewServices.GetRequiredService<PolyTrader.Core.Persistence.IAccountRepository>(),
|
|
viewServices.GetRequiredService<TradingState>());
|
|
return view;
|
|
}
|
|
});
|
|
uiHost.RegisterView(new PolyTrader.Core.Modularity.ModuleView
|
|
{
|
|
Id = "core.jobs",
|
|
Title = "Server Jobs",
|
|
Group = "Core",
|
|
Order = 30,
|
|
CreateForm = () =>
|
|
{
|
|
var view = new PolyTraderSharp.Ui.Views.JobsView();
|
|
view.Initialize(viewServices.GetRequiredService<JobManager>());
|
|
return view;
|
|
}
|
|
});
|
|
uiHost.RegisterView(new PolyTrader.Core.Modularity.ModuleView
|
|
{
|
|
Id = "core.dashboard",
|
|
Title = "Dashboard",
|
|
Group = "Core",
|
|
Order = 5,
|
|
CreateForm = () =>
|
|
{
|
|
var view = new PolyTraderSharp.Ui.Views.DashboardView();
|
|
view.Initialize(
|
|
viewServices.GetRequiredService<PolyTrader.Core.Persistence.ITradeLogRepository>(),
|
|
viewServices.GetRequiredService<TradingState>());
|
|
return view;
|
|
}
|
|
});
|
|
// Modul-UI registrieren (Module steuern ihre Views selbst bei).
|
|
foreach (var module in modules)
|
|
{
|
|
module.RegisterUi(uiHost);
|
|
}
|
|
|
|
var launcher = AppHost.Services.GetRequiredService<PolyTraderSharp.Ui.LauncherForm>();
|
|
Application.Run(launcher);
|
|
AppHost.StopAsync().GetAwaiter().GetResult();
|
|
}
|
|
|
|
private static void RunConfigMigration()
|
|
{
|
|
var config = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
|
|
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
|
|
.AddJsonFile("appsettings.json", optional: true)
|
|
.AddJsonFile("appsettings.Local.json", optional: true)
|
|
.AddEnvironmentVariables()
|
|
.Build();
|
|
|
|
var mongoConn = config["Database:ConnectionString"] ?? "mongodb://localhost:27017";
|
|
var mongoDbName = config["Database:DatabaseName"] ?? "PolyTraderDB";
|
|
var mySql = config["Database:MySqlConnectionString"] ?? string.Empty;
|
|
|
|
if (string.IsNullOrWhiteSpace(mySql))
|
|
{
|
|
Console.WriteLine("FEHLER: Database:MySqlConnectionString fehlt (appsettings.Local.json).");
|
|
return;
|
|
}
|
|
|
|
Services.ConfigMigrator.Run(mongoConn, mongoDbName, mySql);
|
|
}
|
|
|
|
private static void RunConfigMigrationFromJson(string folder)
|
|
{
|
|
var config = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
|
|
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
|
|
.AddJsonFile("appsettings.json", optional: true)
|
|
.AddJsonFile("appsettings.Local.json", optional: true)
|
|
.AddEnvironmentVariables()
|
|
.Build();
|
|
|
|
var mySql = config["Database:MySqlConnectionString"] ?? string.Empty;
|
|
if (string.IsNullOrWhiteSpace(mySql))
|
|
{
|
|
Console.WriteLine("FEHLER: Database:MySqlConnectionString fehlt (appsettings.Local.json).");
|
|
return;
|
|
}
|
|
|
|
if (!System.IO.Path.IsPathRooted(folder))
|
|
folder = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), folder);
|
|
|
|
Services.ConfigMigrator.RunFromJson(folder, mySql);
|
|
}
|
|
|
|
private static void RunVerifyMySql()
|
|
{
|
|
var config = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
|
|
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
|
|
.AddJsonFile("appsettings.json", optional: true)
|
|
.AddJsonFile("appsettings.Local.json", optional: true)
|
|
.AddEnvironmentVariables()
|
|
.Build();
|
|
|
|
var mySql = config["Database:MySqlConnectionString"] ?? string.Empty;
|
|
if (string.IsNullOrWhiteSpace(mySql))
|
|
{
|
|
Console.WriteLine("FEHLER: Database:MySqlConnectionString fehlt (appsettings.Local.json).");
|
|
return;
|
|
}
|
|
|
|
Services.ConfigMigrator.VerifyMySql(mySql);
|
|
}
|
|
}
|