Files
IBKRTrader/Core/Trading/TradingModels.cs
T
Richard 2ad4b55db1 @
Phase 3: Trading-Kern (Risk, Execution, Portfolio) mit sicherem Broker-Default

- Core/Trading/TradingModels: Signal, Order(Request/Result), RiskContext/Decision,
  Account, Position, Quote, ExecutionResult, Enums (Side/OrderType/Mode)
- IBrokerClient + NullBrokerClient (sicherer Default, handelt NIE bis IBKR-Adapter verifiziert)
- RiskService (+IRiskService): Sizing nach MaxTrade%, Modul-Limit, Slippage; Buy/Sell
- PortfolioService (+IPortfolioService): core_position + core_trade_history + core_budget
- ExecutionService (+IExecutionService): Signal -> Kurs -> Konto -> Risiko -> Order -> Buchung
- TradingSettings in AppSettings (Paper/Live, TradingEnabled, Risikoparameter)
- CoreMigrations: core_position; DI-Registrierung der Trading-Services
- Tests: RiskService (11) + ExecutionService (6, NSubstitute) -> 38/38 gruen

Offen (bewusst gekapselt): echter IbkrBrokerClient gegen Client-Portal-Gateway (manuell verifizieren).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-27 11:33:59 +02:00

125 lines
4.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
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.
namespace IBKRTrader.Core.Trading;
/// <summary>Kauf oder Verkauf.</summary>
public enum TradeSide { Buy, Sell }
/// <summary>Order-Typ.</summary>
public enum OrderType { Market, Limit }
/// <summary>Handelsmodus Paper-Account (Test) oder Live.</summary>
public enum TradingMode { Paper, Live }
/// <summary>
/// Signal, das ein Modul an den <see cref="IExecutionService"/> übergibt.
/// Das Modul liefert nur die Absicht Sizing, Risiko und Ausführung macht der Core.
/// </summary>
public sealed record TradeSignal
{
/// <summary>Ticker-Symbol (z. B. "AAPL").</summary>
public required string Symbol { get; init; }
/// <summary>Kauf oder Verkauf.</summary>
public required TradeSide Side { get; init; }
/// <summary>Kürzel des auslösenden Moduls (z. B. "CT").</summary>
public required string SourceModule { get; init; }
/// <summary>Begründung des Signals (für Logging/Buchführung).</summary>
public string Reason { get; init; } = "";
/// <summary>Optionaler Limit-Preis. null = Market-Order.</summary>
public decimal? LimitPrice { get; init; }
/// <summary>Optionaler Nominalwert-Wunsch; sonst greift das Risiko-Sizing.</summary>
public decimal? SuggestedNotional { get; init; }
}
/// <summary>Konkrete Order-Anforderung an den Broker.</summary>
public sealed record OrderRequest
{
public required string Symbol { get; init; }
public required TradeSide Side { get; init; }
public required int Quantity { get; init; }
public required OrderType Type { get; init; }
public decimal? LimitPrice { get; init; }
}
/// <summary>Ergebnis einer Order-Platzierung.</summary>
public sealed record OrderResult
{
public bool Success { get; init; }
public string? OrderId { get; init; }
public int FilledQuantity { get; init; }
public decimal AvgFillPrice { get; init; }
public string? Error { get; init; }
public static OrderResult Filled(string orderId, int qty, decimal price) =>
new() { Success = true, OrderId = orderId, FilledQuantity = qty, AvgFillPrice = price };
public static OrderResult Fail(string error) =>
new() { Success = false, Error = error };
}
/// <summary>Momentaufnahme eines Kurses.</summary>
public sealed record Quote(string Symbol, decimal Last, decimal Bid, decimal Ask);
/// <summary>Kontostand-Momentaufnahme des Brokers.</summary>
public sealed record AccountState(decimal NetLiquidation, decimal AvailableFunds);
/// <summary>Offene Position eines Moduls.</summary>
public sealed record Position(string Module, string Symbol, int Quantity, decimal AvgPrice)
{
public decimal Notional => Quantity * AvgPrice;
}
/// <summary>Kontext für die Risikobewertung eines Signals.</summary>
public sealed record RiskContext
{
/// <summary>Aktueller Kurs des Symbols.</summary>
public required decimal Price { get; init; }
/// <summary>Netto-Liquidationswert des Kontos.</summary>
public required decimal NetLiquidation { get; init; }
/// <summary>Aktuell vom Modul gehaltener Nominalwert (Summe offener Positionen).</summary>
public decimal ModuleExposure { get; init; }
/// <summary>Bereits gehaltene Stückzahl für das Signal-Symbol.</summary>
public int ExistingQuantity { get; init; }
}
/// <summary>Aus den Settings abgeleitete Risiko-Parameter.</summary>
public sealed record RiskParameters(
decimal MaxTradePercent,
decimal MaxPositionPercentPerModule,
decimal MaxSlippagePercent);
/// <summary>Entscheidung der Risikoprüfung.</summary>
public sealed record RiskDecision
{
public bool Approved { get; init; }
public int Quantity { get; init; }
public string Reason { get; init; } = "";
public static RiskDecision Reject(string reason) =>
new() { Approved = false, Quantity = 0, Reason = reason };
public static RiskDecision Approve(int quantity, string reason = "OK") =>
new() { Approved = true, Quantity = quantity, Reason = reason };
}
/// <summary>Ergebnis einer Signal-Ausführung durch den <see cref="IExecutionService"/>.</summary>
public sealed record ExecutionResult
{
/// <summary>"EXECUTE", "SKIP" oder "ERROR".</summary>
public required string Action { get; init; }
public string Reason { get; init; } = "";
public OrderResult? Order { get; init; }
public bool Executed => Action == "EXECUTE";
public static ExecutionResult Skip(string reason) => new() { Action = "SKIP", Reason = reason };
public static ExecutionResult Error(string reason, OrderResult? order = null) => new() { Action = "ERROR", Reason = reason, Order = order };
public static ExecutionResult Execute(OrderResult order) => new() { Action = "EXECUTE", Reason = "OK", Order = order };
}