Bindet Lizenz, Watchdog, Fehler-Stream, UpdateService und Erstinstallation an
das Deploymentcenter 2.5.1 an. Einbauort ist IBKRTrader.Hosting - den Host
teilen sich Shell und Daemon.
Projekt-Befunde aus dem Konzept vorab bereinigt:
P1 Echte DB-Zugangsdaten als Vorgabewerte in AppSettings -> Platzhalter.
Das alte Passwort steht weiterhin in der Git-Historie und ist als
kompromittiert zu behandeln (Rotation ist Nutzer-Aktion).
P2 AppPaths fiel unter Windows auf /etc/ibkrtrader zurueck, was .NET zu
C:\etc\ibkrtrader aufloest. Jetzt %ProgramData%\IBKRTrader.
P3 Globale Ausnahmebehandler (AppDomain / TaskScheduler) - vorher gab es
keinen Logeintrag, wenn der Prozess unbehandelt wegbrach.
P4 Version einmal zentral in Directory.Build.props statt zweimal hartkodiert.
Bewusste Abweichungen vom DC-Leitfaden, beide fuer ein handelndes System:
- Lizenz-Urteil fuehrt zum Sperrbetrieb (TradingEnabled=false) statt zu
Environment.Exit(1). Keine neuen Einstiege, aber Risiko-, Exit- und
Buchhaltungslogik laufen weiter.
- exitCurrentApp bleibt immer false; der Aufrufer beendet geordnet.
Das SDK haengt als Cross-Repo-ProjectReference am Schwester-Repo
Deploymentcenter (Interim, siehe Konzept 2.2). Damit ist P5 offen: die
Gitea-CI checkt das Schwester-Repo nicht aus und wird rot, bis der Bezug auf
ein NuGet-Paket umgestellt ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
411 lines
17 KiB
C#
411 lines
17 KiB
C#
using System.ComponentModel;
|
||
using IBKRTrader.Core.Trading;
|
||
|
||
namespace IBKRTrader.Core.Settings;
|
||
|
||
// ─── Database ────────────────────────────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class DatabaseSettings
|
||
{
|
||
[Category("Database")]
|
||
[DisplayName("Host")]
|
||
[Description("MySQL-Serveradresse (Hostname oder IP)")]
|
||
public string Host { get; set; } = "your-db-host";
|
||
|
||
[Category("Database")]
|
||
[DisplayName("Port")]
|
||
[Description("MySQL-Port (Standard: 3306)")]
|
||
public int Port { get; set; } = 3306;
|
||
|
||
[Category("Database")]
|
||
[DisplayName("Database")]
|
||
[Description("Name der MySQL-Datenbank")]
|
||
public string Database { get; set; } = "your-db-name";
|
||
|
||
[Category("Database")]
|
||
[DisplayName("User")]
|
||
[Description("MySQL-Benutzername")]
|
||
public string User { get; set; } = "your-db-user";
|
||
|
||
[Category("Database")]
|
||
[DisplayName("Password")]
|
||
[Description("MySQL-Passwort")]
|
||
[PasswordPropertyText(true)]
|
||
public string Password { get; set; } = "CHANGE_ME";
|
||
|
||
public string BuildConnectionString() =>
|
||
$"Server={Host};Port={Port};Database={Database};" +
|
||
$"User={User};Password={Password};" +
|
||
$"AllowPublicKeyRetrieval=true;SslMode=Preferred;CharSet=utf8mb4;" +
|
||
$"ConnectionTimeout=15;DefaultCommandTimeout=30;";
|
||
|
||
public override string ToString() => $"{User}@{Host}:{Port}/{Database}";
|
||
}
|
||
|
||
// ─── IBKR TWS/Gateway (Socket API) ───────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class IBKRSettings
|
||
{
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("Host")]
|
||
[Description("Adresse des IBKR Gateway (Standard: 127.0.0.1)")]
|
||
public string Host { get; set; } = "127.0.0.1";
|
||
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("Port")]
|
||
[Description("IBKR Gateway Port (Paper: 4002, Live: 4001)")]
|
||
public int Port { get; set; } = 4001;
|
||
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("Client ID")]
|
||
[Description("Eindeutige Client-ID für die API-Verbindung")]
|
||
public int ClientId { get; set; } = 1;
|
||
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("TWS-Broker verwenden")]
|
||
[Description("Aktiviert den echten TWS-Broker. Aus = NullBroker (handelt nie). " +
|
||
"Der globale Handelsschalter unter Trading bleibt davon unberührt.")]
|
||
public bool UseTwsApi { get; set; } = false;
|
||
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("Marktdaten-Typ")]
|
||
[Description("1 = Realtime, 2 = Frozen, 3 = verzögert, 4 = verzögert+Frozen. " +
|
||
"Paper-Konten ohne Datenabo brauchen 3 oder 4, sonst kommen keine Kurse.")]
|
||
public int MarketDataType { get; set; } = 4;
|
||
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("Verbindungs-Timeout (s)")]
|
||
[Description("Wartezeit auf Socket-Verbindung und Handshake. TWS braucht nach einem Neustart mehrere Minuten.")]
|
||
public int ConnectTimeoutSeconds { get; set; } = 15;
|
||
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("Anfrage-Timeout (s)")]
|
||
[Description("Wartezeit auf Kurs-, Kontrakt- und Kontoantworten")]
|
||
public int RequestTimeoutSeconds { get; set; } = 15;
|
||
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("Order-Timeout (s)")]
|
||
[Description("Wartezeit auf den Endstatus einer Order. Danach gilt sie als fehlgeschlagen, " +
|
||
"kann bei IBKR aber weiterhin aktiv sein.")]
|
||
public int OrderTimeoutSeconds { get; set; } = 60;
|
||
|
||
public override string ToString() =>
|
||
$"{Host}:{Port} (Client {ClientId}){(UseTwsApi ? "" : " – TWS-Broker aus")}";
|
||
}
|
||
|
||
// ─── IBKR Web API (Client Portal Gateway) ────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class IBKRWebApiSettings
|
||
{
|
||
[Category("IBKR Web API")]
|
||
[DisplayName("Aktiviert")]
|
||
[Description("Aktiviert die IBKR Web API Marktdaten-Worker")]
|
||
public bool Enabled { get; set; } = false;
|
||
|
||
[Category("IBKR Web API")]
|
||
[DisplayName("Gateway URL")]
|
||
[Description("URL des Client Portal Gateway (Standard: https://localhost:5000)")]
|
||
public string GatewayUrl { get; set; } = "https://localhost:5000";
|
||
|
||
[Category("IBKR Web API")]
|
||
[DisplayName("History-Zeitraum")]
|
||
[Description("Zeitraum für den ersten historischen Import (z.B. 2Y, 5Y, 1Y)")]
|
||
public string HistoryPeriod { get; set; } = "2Y";
|
||
|
||
[Category("IBKR Web API")]
|
||
[DisplayName("Max Requests/Sek")]
|
||
[Description("Maximale API-Anfragen pro Sekunde (IBKR-Limit: 10, empfohlen: 5)")]
|
||
public int MaxRequestsPerSecond { get; set; } = 5;
|
||
|
||
public override string ToString() => Enabled ? $"Aktiv – {GatewayUrl}" : "Deaktiviert";
|
||
}
|
||
|
||
// ─── Logging ─────────────────────────────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class LoggingSettings
|
||
{
|
||
[Category("Logging")]
|
||
[DisplayName("Log Level")]
|
||
[Description("Minimales Log-Level: Info, Warn oder Error")]
|
||
public string Level { get; set; } = "Info";
|
||
|
||
[Category("Logging")]
|
||
[DisplayName("Max. Dateigröße (MB)")]
|
||
[Description("Maximale Größe einer Log-Datei in Megabyte")]
|
||
public int MaxFileSizeMb { get; set; } = 10;
|
||
|
||
[Category("Logging")]
|
||
[DisplayName("Aufbewahrung (Tage)")]
|
||
[Description("Anzahl Tage, nach denen Log-Dateien gelöscht werden")]
|
||
public int RetentionDays { get; set; } = 30;
|
||
|
||
public override string ToString() => $"Level: {Level}";
|
||
}
|
||
|
||
// ─── Webserver ───────────────────────────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class WebserverSettings
|
||
{
|
||
[Category("Webserver")]
|
||
[DisplayName("Port")]
|
||
[Description("Port des internen Webservers (Web-UI)")]
|
||
public int Port { get; set; } = 5001;
|
||
|
||
[Category("Webserver")]
|
||
[DisplayName("Aktiviert")]
|
||
[Description("Webserver und REST-API beim Start aktivieren")]
|
||
public bool Enabled { get; set; } = false;
|
||
|
||
public override string ToString() => Enabled ? $"Aktiv auf Port {Port}" : "Deaktiviert";
|
||
}
|
||
|
||
// ─── Workers ─────────────────────────────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class WorkerSchedule
|
||
{
|
||
[Category("Worker")]
|
||
[DisplayName("Aktiviert")]
|
||
public bool Enabled { get; set; } = true;
|
||
|
||
[Category("Worker")]
|
||
[DisplayName("Interval (Minuten)")]
|
||
public int IntervalMinutes { get; set; } = 30;
|
||
|
||
public override string ToString() => Enabled ? $"alle {IntervalMinutes} min" : "Deaktiviert";
|
||
}
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class WorkerSettings
|
||
{
|
||
[Category("Workers")]
|
||
[DisplayName("BackupWorker")]
|
||
[Description("Zeitplan für den automatischen Backup-Worker")]
|
||
public WorkerSchedule BackupWorker { get; set; } = new();
|
||
|
||
[Category("Workers")]
|
||
[DisplayName("IBKR Instrument-Sync")]
|
||
[Description("Zeitplan für die IBKR Stammdaten-Synchronisation (alle 30 Tage)")]
|
||
public WorkerSchedule InstrumentSyncWorker { get; set; } = new() { IntervalMinutes = 43200 }; // 30 Tage
|
||
|
||
[Category("Workers")]
|
||
[DisplayName("IBKR Preishistorie")]
|
||
[Description("Zeitplan für den täglichen IBKR Preishistorie-Abruf")]
|
||
public WorkerSchedule PriceHistoryWorker { get; set; } = new() { IntervalMinutes = 1440 }; // 24h
|
||
|
||
public override string ToString() => "Worker-Konfiguration";
|
||
}
|
||
|
||
// ─── Trading ─────────────────────────────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class TradingSettings
|
||
{
|
||
[Category("Trading")]
|
||
[DisplayName("Modus")]
|
||
[Description("Handelsmodus: Paper (Test-Account, Port 4002) oder Live (Port 4001)")]
|
||
public string Mode { get; set; } = "Paper";
|
||
|
||
[Category("Trading")]
|
||
[DisplayName("Betriebszeitzone")]
|
||
[Description("Zeitzone dieser Instanz in IANA-Schreibweise, z. B. \"Europe/Berlin\" (EU) oder " +
|
||
"\"America/New_York\" (US). Gilt für Anzeige, Logdatei-Tagesgrenzen, Berichtszeiten " +
|
||
"und Buchungsperioden; gespeichert wird immer UTC. VOR den ersten Trades festlegen " +
|
||
"und danach nicht mehr ändern – ein Wechsel verschiebt rückwirkend alle Tagesgrenzen. " +
|
||
"Leer = Zeitzone des Systems (nicht empfohlen). Wirkt erst nach einem Neustart.")]
|
||
public string ApplicationTimeZoneId { get; set; } = "Europe/Berlin";
|
||
|
||
[Category("Trading")]
|
||
[DisplayName("Trading aktiv")]
|
||
[Description("Globaler Hauptschalter. Nur wenn aktiv werden Orders ausgeführt.")]
|
||
public bool TradingEnabled { get; set; } = false;
|
||
|
||
[Category("Trading")]
|
||
[DisplayName("Max. je Trade (%)")]
|
||
[Description("Maximaler Nominalwert einer einzelnen Position in % des Kontowerts")]
|
||
public double MaxTradePercent { get; set; } = 5.0;
|
||
|
||
[Category("Trading")]
|
||
[DisplayName("Max. je Modul (%)")]
|
||
[Description("Maximaler Gesamt-Nominalwert aller Positionen eines Moduls in % des Kontowerts")]
|
||
public double MaxPositionPercentPerModule { get; set; } = 20.0;
|
||
|
||
[Category("Trading")]
|
||
[DisplayName("Max. Slippage (%)")]
|
||
[Description("Maximal erlaubte Abweichung zwischen Limit-Preis und aktuellem Kurs")]
|
||
public double MaxSlippagePercent { get; set; } = 5.0;
|
||
|
||
[Category("Trading")]
|
||
[DisplayName("Gewinnziel (%)")]
|
||
[Description("Ziel-Gewinn einer Position in Prozent (für spätere Exit-Logik)")]
|
||
public double ProfitTargetPercent { get; set; } = 50.0;
|
||
|
||
/// <summary>Parst den Modus in das Enum (Fallback: Paper).</summary>
|
||
public TradingMode ParsedMode =>
|
||
Enum.TryParse<TradingMode>(Mode, true, out var m) ? m : TradingMode.Paper;
|
||
|
||
public override string ToString() => $"{Mode} – {(TradingEnabled ? "aktiv" : "inaktiv")}";
|
||
}
|
||
|
||
// ─── Accounting ──────────────────────────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class AccountingSettings
|
||
{
|
||
[Category("Accounting")]
|
||
[DisplayName("Basiswährung")]
|
||
[Description("Kontobasiswährung für die Abrechnung (Standard: USD)")]
|
||
public string BaseCurrency { get; set; } = "USD";
|
||
|
||
[Category("Accounting")]
|
||
[DisplayName("Ingest-Intervall (Stunden)")]
|
||
[Description("Abstand der automatischen Kontoauszug-Abrufe (Live-Quelle; offline ungenutzt)")]
|
||
public int IngestIntervalHours { get; set; } = 6;
|
||
|
||
[Category("Accounting")]
|
||
[DisplayName("Flex Query-Id")]
|
||
[Description("IBKR Activity Flex Query-Id (Zielland – Live-Abruf; leer = offline)")]
|
||
public string FlexQueryId { get; set; } = "";
|
||
|
||
[Category("Accounting")]
|
||
[DisplayName("Flex-Token")]
|
||
[Description("IBKR Flex Web Service Token (Zielland; verschlüsselt ablegen, nicht im Klartext)")]
|
||
[PasswordPropertyText(true)]
|
||
public string FlexToken { get; set; } = "";
|
||
|
||
public override string ToString() => $"Basis {BaseCurrency}, alle {IngestIntervalHours} h";
|
||
}
|
||
|
||
// ─── Supervisor ──────────────────────────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class SupervisorSettings
|
||
{
|
||
[Category("Supervisor")]
|
||
[DisplayName("Modell")]
|
||
[Description("OpenRouter-Modell für die Analyse (Standard: openrouter/auto)")]
|
||
public string Model { get; set; } = "openrouter/auto";
|
||
|
||
[Category("Supervisor")]
|
||
[DisplayName("Token-Budget/Analyse")]
|
||
[Description("Weiches Token-Budget je Analyse-Lauf (Hinweis; harte Grenze = max. Tool-Iterationen)")]
|
||
public int TokenBudgetPerRun { get; set; } = 60000;
|
||
|
||
public override string ToString() => Model;
|
||
}
|
||
|
||
// ─── Deploymentcenter ────────────────────────────────────────────────────────
|
||
|
||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||
public class DeploymentcenterSettings
|
||
{
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("API Token")]
|
||
[Description("Authorization: Bearer <Token>. Ohne Token: keine Heartbeats, keine Fehlermeldungen.")]
|
||
[PasswordPropertyText(true)]
|
||
public string ApiToken { get; set; } = "";
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Heartbeat aktiv")]
|
||
[Description("Dead-Man's-Switch an/aus")]
|
||
public bool HeartbeatEnabled { get; set; } = true;
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Monitor Source")]
|
||
[Description("Monitor-Name im Dashboard. Leer = Rechnername (Auto-Registrierung beim ersten Ping)")]
|
||
public string MonitorSource { get; set; } = "";
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Monitor Instance")]
|
||
[Description("Für mehrere Instanzen desselben Dienstes (z. B. EU/US)")]
|
||
public string MonitorInstance { get; set; } = "default";
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Heartbeat-Intervall (Sekunden)")]
|
||
[Description("warning nach 2×, down nach 4× dieses Intervalls")]
|
||
public int HeartbeatIntervalSeconds { get; set; } = 60;
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Fehler melden")]
|
||
[Description("Error/Fatal an den Fehler-Stream (/api/errors/v1/report)")]
|
||
public bool ReportErrors { get; set; } = true;
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Update-Prüfung beim Start")]
|
||
[Description("Prüft nur, installiert nichts von selbst")]
|
||
public bool CheckUpdatesOnStart { get; set; } = true;
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Update-Kanal")]
|
||
[Description("prod, beta oder dev")]
|
||
public string UpdateChannel { get; set; } = "prod";
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Lizenzschlüssel")]
|
||
[Description("Nur Rückfall, falls noch kein Schlüssel im verschlüsselten Cache liegt")]
|
||
[PasswordPropertyText(true)]
|
||
public string LicenseKey { get; set; } = "";
|
||
|
||
public override string ToString() => HeartbeatEnabled ? "Aktiv" : "Deaktiviert";
|
||
}
|
||
|
||
// ─── Root ────────────────────────────────────────────────────────────────────
|
||
|
||
public class AppSettings
|
||
{
|
||
[Category("Database")]
|
||
[DisplayName("Datenbank")]
|
||
[Description("MySQL-Verbindungseinstellungen")]
|
||
public DatabaseSettings Database { get; set; } = new();
|
||
|
||
[Category("IBKR Gateway")]
|
||
[DisplayName("IBKR")]
|
||
[Description("Interactive Brokers Gateway-Verbindung (Socket API)")]
|
||
public IBKRSettings IBKR { get; set; } = new();
|
||
|
||
[Category("IBKR Web API")]
|
||
[DisplayName("IBKR Web API")]
|
||
[Description("Client Portal Gateway für Marktdaten")]
|
||
public IBKRWebApiSettings IBKRWebApi { get; set; } = new();
|
||
|
||
[Category("Logging")]
|
||
[DisplayName("Logging")]
|
||
[Description("Log-Level und Datei-Einstellungen")]
|
||
public LoggingSettings Logging { get; set; } = new();
|
||
|
||
[Category("Webserver")]
|
||
[DisplayName("Webserver")]
|
||
[Description("Interner HTTP-Server und REST-API")]
|
||
public WebserverSettings Webserver { get; set; } = new();
|
||
|
||
[Category("Workers")]
|
||
[DisplayName("Worker-Einstellungen")]
|
||
[Description("Zeitpläne der einzelnen Core-Worker")]
|
||
public WorkerSettings WorkerSettings { get; set; } = new();
|
||
|
||
[Category("Trading")]
|
||
[DisplayName("Trading")]
|
||
[Description("Handelsmodus und Risiko-Parameter")]
|
||
public TradingSettings Trading { get; set; } = new();
|
||
|
||
[Category("Accounting")]
|
||
[DisplayName("Accounting")]
|
||
[Description("Buchhaltung/Reporting (unabhängiger Kontoauszug-Ingest)")]
|
||
public AccountingSettings Accounting { get; set; } = new();
|
||
|
||
[Category("Supervisor")]
|
||
[DisplayName("Supervisor")]
|
||
[Description("KI-Analyse/Forensik (OpenRouter, read-only)")]
|
||
public SupervisorSettings Supervisor { get; set; } = new();
|
||
|
||
[Category("Deploymentcenter")]
|
||
[DisplayName("Deploymentcenter")]
|
||
[Description("Lizenz, Watchdog, UpdateService, Fehler-Stream (https://dc.mhdf.de)")]
|
||
public DeploymentcenterSettings Deploymentcenter { get; set; } = new();
|
||
}
|