Slice 0 (Fable-Fixes): IClobClient-Seam fuer testbare CLOB-Interaktionen
Verhaltensneutrale Testinfrastruktur als Grundlage fuer Slice 1/2: - IClobClient-Interface (Core) ueber die von Leiter/Reconciliation genutzten CLOB-Methoden (Place/CancelConflicting/GetOpenOrders/CancelOrder). - PolymarketClobClient implementiert IClobClient (Signaturen unveraendert). - DI-Seam im Modul: IClobClient -> PolymarketClobClient-Singleton. - SellLadderService haengt jetzt an IClobClient (statische CalculateExactOrderAmounts bleibt am konkreten Typ). - InternalsVisibleTo(PolyTrader.Tests) + FakeClobClient-Test-Double. Verifikation: Build 0 Fehler, 207 Tests gruen, --smoke-ui Container-Aufbau ok. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
bb929fa23a
commit
ad8f7b0d03
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
|
namespace PolyTraderSharp.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Schmales Interface über die CLOB-Order-Operationen, die die SELL-Eskalationsleiter und die
|
||||||
|
/// Startup-Reconciliation nutzen. Zweck: Diese geldkritischen Service-Interaktionen (Leiter vs.
|
||||||
|
/// Cleanup, Neustart-Cancel) über einen gemockten Client integrationstestbar machen, ohne den
|
||||||
|
/// echten <see cref="PolymarketClobClient"/> anzufassen.
|
||||||
|
///
|
||||||
|
/// Bewusst minimal – nur die tatsächlich von den Modul-Services aufgerufenen Methoden. Die
|
||||||
|
/// Preis-/Mengenberechnung bleibt als statische, reine Methode
|
||||||
|
/// (<see cref="PolymarketClobClient.CalculateExactOrderAmounts"/>) außerhalb des Interfaces.
|
||||||
|
/// </summary>
|
||||||
|
public interface IClobClient
|
||||||
|
{
|
||||||
|
/// <summary>Platziert eine Order und liefert "OK" oder eine Fehlerbeschreibung.</summary>
|
||||||
|
Task<string> PlaceOrderAsync(
|
||||||
|
AccountState account, string tokenId, string sideStr, decimal investAmountUsd, decimal limitPrice,
|
||||||
|
string orderType = "FOK", bool debugPayloadLog = false, bool isNegRisk = false, int actualFeeBps = 0,
|
||||||
|
decimal? overrideTickSize = null, int? overrideMakerDecimals = null, int? overrideTakerDecimals = null);
|
||||||
|
|
||||||
|
/// <summary>Cancelt konfligierende offene Orders desselben Tokens vor dem Nachpreisen/Platzieren.</summary>
|
||||||
|
Task CancelConflictingOrdersAsync(AccountState acc, string assetId, decimal newPrice, string sideStr);
|
||||||
|
|
||||||
|
/// <summary>Liefert die offenen Orders (Id, Side, Price) für ein Asset.</summary>
|
||||||
|
Task<List<(string Id, string Side, decimal Price)>> GetOpenOrdersAsync(AccountState acc, string assetId);
|
||||||
|
|
||||||
|
/// <summary>Cancelt eine einzelne Order per OrderId; true bei Erfolg.</summary>
|
||||||
|
Task<bool> CancelOrderAsync(AccountState acc, string orderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,7 +100,7 @@ namespace PolyTraderSharp.Services
|
|||||||
public byte SignatureType { get; set; }
|
public byte SignatureType { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PolymarketClobClient
|
public class PolymarketClobClient : IClobClient
|
||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly TerminalLogger _logger;
|
private readonly TerminalLogger _logger;
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ namespace PolyTrader.Modules.CopyTrading
|
|||||||
services.AddHostedService<AlchemyWebsocketService>();
|
services.AddHostedService<AlchemyWebsocketService>();
|
||||||
services.AddHostedService<PolymarketWssClient>();
|
services.AddHostedService<PolymarketWssClient>();
|
||||||
|
|
||||||
|
// IClobClient-Seam: Leiter/Reconciliation hängen am Interface (mockbar für Integrationstests);
|
||||||
|
// die Live-Instanz ist der eine PolymarketClobClient-Singleton aus dem App-Container.
|
||||||
|
services.AddSingleton<IClobClient>(sp => sp.GetRequiredService<PolymarketClobClient>());
|
||||||
|
|
||||||
// Phase 0.1: SELL-Eskalationsleiter (preist offene Exit-Limits stufenweise nach).
|
// Phase 0.1: SELL-Eskalationsleiter (preist offene Exit-Limits stufenweise nach).
|
||||||
// Singleton + Hosted, damit Engine und TraderMonitor StartLadderAsync aufrufen können.
|
// Singleton + Hosted, damit Engine und TraderMonitor StartLadderAsync aufrufen können.
|
||||||
services.AddSingleton<SellLadderService>();
|
services.AddSingleton<SellLadderService>();
|
||||||
|
|||||||
@@ -4,6 +4,13 @@
|
|||||||
<ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" />
|
<ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Erlaubt dem Testprojekt, interne Service-Methoden (z.B. Leiter-Tick) integrationszutesten. -->
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||||
|
<_Parameter1>PolyTrader.Tests</_Parameter1>
|
||||||
|
</AssemblyAttribute>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace PolyTraderSharp.Services
|
|||||||
|
|
||||||
private readonly CopyTradingState _copyState;
|
private readonly CopyTradingState _copyState;
|
||||||
private readonly TradingState _state;
|
private readonly TradingState _state;
|
||||||
private readonly PolymarketClobClient _clob;
|
private readonly IClobClient _clob;
|
||||||
private readonly TerminalLogger _logger;
|
private readonly TerminalLogger _logger;
|
||||||
private readonly ThreemaService _threema;
|
private readonly ThreemaService _threema;
|
||||||
private readonly IPositionRepository _positionRepo;
|
private readonly IPositionRepository _positionRepo;
|
||||||
@@ -35,7 +35,7 @@ namespace PolyTraderSharp.Services
|
|||||||
public SellLadderService(
|
public SellLadderService(
|
||||||
CopyTradingState copyState,
|
CopyTradingState copyState,
|
||||||
TradingState state,
|
TradingState state,
|
||||||
PolymarketClobClient clob,
|
IClobClient clob,
|
||||||
TerminalLogger logger,
|
TerminalLogger logger,
|
||||||
ThreemaService threema,
|
ThreemaService threema,
|
||||||
IPositionRepository positionRepo)
|
IPositionRepository positionRepo)
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
using PolyTraderSharp.Services;
|
||||||
|
|
||||||
|
namespace PolyTrader.Tests.Fakes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// In-Memory-Stub für <see cref="IClobClient"/>. Zeichnet Aufrufe auf und liefert steuerbare
|
||||||
|
/// Antworten, damit die Service-Interaktionen (SELL-Leiter, Startup-Reconciliation) ohne echten
|
||||||
|
/// CLOB integrationstestbar sind. Bewusst simpel und synchron (Task.FromResult).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class FakeClobClient : IClobClient
|
||||||
|
{
|
||||||
|
public sealed record PlacedOrder(string TokenId, string Side, decimal Usdc, decimal Price, string OrderType);
|
||||||
|
|
||||||
|
// Aufzeichnungen
|
||||||
|
public List<PlacedOrder> Placed { get; } = new();
|
||||||
|
public List<string> CanceledOrderIds { get; } = new();
|
||||||
|
public List<(string AssetId, decimal NewPrice, string Side)> ConflictCancels { get; } = new();
|
||||||
|
|
||||||
|
// Steuerbare Antworten
|
||||||
|
/// <summary>Ergebnis von PlaceOrderAsync (Default "OK"). Queue hat Vorrang, sonst dieser Wert.</summary>
|
||||||
|
public string PlaceResult { get; set; } = "OK";
|
||||||
|
public Queue<string> PlaceResults { get; } = new();
|
||||||
|
public bool CancelResult { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>Offene Orders je Asset, die GetOpenOrdersAsync zurückgibt.</summary>
|
||||||
|
public Dictionary<string, List<(string Id, string Side, decimal Price)>> OpenOrdersByAsset { get; } = new();
|
||||||
|
|
||||||
|
public Task<string> PlaceOrderAsync(
|
||||||
|
AccountState account, string tokenId, string sideStr, decimal investAmountUsd, decimal limitPrice,
|
||||||
|
string orderType = "FOK", bool debugPayloadLog = false, bool isNegRisk = false, int actualFeeBps = 0,
|
||||||
|
decimal? overrideTickSize = null, int? overrideMakerDecimals = null, int? overrideTakerDecimals = null)
|
||||||
|
{
|
||||||
|
Placed.Add(new PlacedOrder(tokenId, sideStr, investAmountUsd, limitPrice, orderType));
|
||||||
|
string result = PlaceResults.Count > 0 ? PlaceResults.Dequeue() : PlaceResult;
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CancelConflictingOrdersAsync(AccountState acc, string assetId, decimal newPrice, string sideStr)
|
||||||
|
{
|
||||||
|
ConflictCancels.Add((assetId, newPrice, sideStr));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<(string Id, string Side, decimal Price)>> GetOpenOrdersAsync(AccountState acc, string assetId)
|
||||||
|
{
|
||||||
|
if (OpenOrdersByAsset.TryGetValue(assetId, out var list))
|
||||||
|
return Task.FromResult(new List<(string, string, decimal)>(list));
|
||||||
|
return Task.FromResult(new List<(string, string, decimal)>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> CancelOrderAsync(AccountState acc, string orderId)
|
||||||
|
{
|
||||||
|
CanceledOrderIds.Add(orderId);
|
||||||
|
return Task.FromResult(CancelResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user