Eigentumsregeln 3 und 4: gebundener Ausfuehrungszugang und eine Grenze mit Zaehnen
Regel 3 verlangt, dass die Buchzuordnung serverseitig gesetzt wird und TradeSignal.SourceModule nicht vertrauenswuerdig ist. Bisher war nur der unbekannte Name abgefangen; ein Modul konnte weiterhin das Kuerzel eines ANDEREN registrierten Moduls eintragen und dessen Cap, dessen Konzentration und dessen GuV belasten. Jedes Modul bekommt jetzt einen an sein Buch gebundenen IModuleExecution-Zugang statt des gemeinsamen IExecutionService. Der Zugang ueberschreibt die Herkunft mit seinem eigenen Buch - was im Signal steht, spielt keine Rolle mehr. Der Zugang steht ausdruecklich NICHT im Container: was dort steht, kann jedes Modul aufloesen, auch den Zugang eines anderen. Der Host uebergibt stattdessen eine Fabrik beim Registrieren (neue Ueberladung von IModule.RegisterServices mit Vorgabe, damit ein nicht handelndes Modul nichts davon wissen muss), und das Modul reicht den erzeugten Zugang direkt an seine Strategie. Regel 4 - Module bekommen den Broker nicht per DI - war eingehalten, aber nicht erzwungen. Jetzt zwei Tests ueber alle vier Modul-Assemblies: der eine prueft per Reflection Konstruktorparameter, Felder und Eigenschaften auf IBrokerClient, IBrokerPortfolioReader und IExecutionService; der andere durchsucht die Modulquellen nach dem zweiten Weg, dem Griff in den Container. Beide Gegenproben mit einem absichtlichen Regelbruch gefahren: jeder Test schlaegt bei genau seinem Weg an. Getrennte Container je Modul waeren der harte Weg, aendern aber die Modularchitektur. Der Fall, um den es geht, ist ein NEUES Modul, das den Core versehentlich umgeht - und den faengt der Test beim Uebersetzen. 404 Tests gruen, auf Windows und auf der Test-VM. Behebt #474, damit ist auch #302 vollstaendig. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,20 @@ public interface IModule
|
||||
/// </summary>
|
||||
void RegisterServices(IServiceCollection services, IConfiguration configuration);
|
||||
|
||||
/// <summary>
|
||||
/// Wie oben, zusätzlich mit der Fabrik für den <b>an das Buch des Moduls gebundenen</b>
|
||||
/// Ausführungszugang. Der Host ruft diese Fassung; die Vorgabe reicht an die einfache weiter,
|
||||
/// damit ein Modul, das gar nicht handelt, nichts davon wissen muss.
|
||||
///
|
||||
/// <para>Ein handelndes Modul überschreibt sie und gibt den Zugang an seine Strategie weiter –
|
||||
/// er gehört ausdrücklich <b>nicht</b> in den Container. Was dort steht, kann jedes Modul
|
||||
/// auflösen, auch der Zugang eines anderen, und damit auf dessen Buch handeln
|
||||
/// (Kapitalmodell §2 Regel 3).</para>
|
||||
/// </summary>
|
||||
void RegisterServices(IServiceCollection services, IConfiguration configuration,
|
||||
Trading.ModuleExecutionFactory execution)
|
||||
=> RegisterServices(services, configuration);
|
||||
|
||||
/// <summary>
|
||||
/// Registriert die UI-Ansichten des Moduls bei der Shell (Launcher). Die Shell öffnet jede Ansicht
|
||||
/// auf Wunsch in einem eigenen Fenster; Module liefern eigene Forms, die Shell bleibt modul-agnostisch.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using IBKRTrader.Core.Capital;
|
||||
|
||||
namespace IBKRTrader.Core.Trading;
|
||||
|
||||
/// <summary>
|
||||
/// Der Ausführungszugang eines <b>einzelnen</b> Moduls – fest an dessen Buch gebunden.
|
||||
///
|
||||
/// <para><b>Warum nicht einfach <see cref="IExecutionService"/>.</b> Dort trägt das Modul seine
|
||||
/// Herkunft selbst im Signal ein (<c>TradeSignal.SourceModule</c>), und ein Feld, das der
|
||||
/// Absender füllt, ist kein Ausweis. Ein Modul könnte das Kürzel eines anderen eintragen; die
|
||||
/// Position liefe dann auf dessen Cap, dessen Konzentration und dessen GuV. Kein heutiges Modul
|
||||
/// tut das – aber das Kapitalmodell verlangt in §2 Regel 3 ausdrücklich, dass die Zuordnung
|
||||
/// <i>serverseitig</i> gesetzt wird, und eine Regel, auf deren Einhaltung man hofft, ist keine.</para>
|
||||
///
|
||||
/// <para>Der Host erzeugt je Modul genau eine Instanz und übergibt sie beim Registrieren. Sie
|
||||
/// steht <b>nicht</b> im gemeinsamen Container: Ein Modul kann den Zugang eines anderen deshalb
|
||||
/// nicht auflösen.</para>
|
||||
/// </summary>
|
||||
public interface IModuleExecution
|
||||
{
|
||||
/// <summary>Das Buch, auf das jede Ausführung dieses Zugangs läuft.</summary>
|
||||
BookId Book { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Übergibt ein Signal an die Ausführungs-Pipeline des Cores. Die Herkunft wird dabei
|
||||
/// überschrieben – was im Signal steht, spielt keine Rolle.
|
||||
/// </summary>
|
||||
Task<ExecutionResult> ExecuteAsync(TradeSignal signal, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bindet ein Modul an sein Buch: setzt die Herkunft und reicht weiter.
|
||||
///
|
||||
/// <para>Bewusst kein Filter und keine zweite Prüfung – der Zugang soll nichts anderes tun, als
|
||||
/// das eine Feld zu setzen, das ein Modul nicht selbst bestimmen darf. Alles Weitere entscheidet
|
||||
/// die Pipeline, für jedes Modul gleich.</para>
|
||||
/// </summary>
|
||||
public sealed class ModuleExecution : IModuleExecution
|
||||
{
|
||||
private readonly IExecutionService _inner;
|
||||
|
||||
public ModuleExecution(BookId book, IExecutionService inner)
|
||||
{
|
||||
Book = book;
|
||||
_inner = inner;
|
||||
}
|
||||
|
||||
public BookId Book { get; }
|
||||
|
||||
public Task<ExecutionResult> ExecuteAsync(TradeSignal signal, CancellationToken ct = default) =>
|
||||
_inner.ExecuteAsync(signal with { SourceModule = Book.Value }, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erzeugt den gebundenen Ausführungszugang eines Moduls – erst dann, wenn der Container steht.
|
||||
///
|
||||
/// <para>Beim Registrieren gibt es noch keinen <see cref="IServiceProvider"/>; das Modul bekommt
|
||||
/// deshalb die Fabrik und ruft sie in seiner eigenen Dienst-Registrierung auf. Der Zugang landet
|
||||
/// so <b>in der Strategie des Moduls</b> statt im Container – und ist damit für kein anderes
|
||||
/// Modul auflösbar.</para>
|
||||
/// </summary>
|
||||
public sealed class ModuleExecutionFactory
|
||||
{
|
||||
public ModuleExecutionFactory(BookId book) => Book = book;
|
||||
|
||||
/// <summary>Das Buch, an das jeder erzeugte Zugang gebunden ist.</summary>
|
||||
public BookId Book { get; }
|
||||
|
||||
public IModuleExecution Create(IServiceProvider services) =>
|
||||
new ModuleExecution(Book, (IExecutionService)services.GetService(typeof(IExecutionService))!);
|
||||
}
|
||||
Reference in New Issue
Block a user