Phase 0.2: Fee-Modell-Fundament (FeeModel + MarketData.TakerFeeBps + BUY-Fee-Log)
- FeeModel (pure, getestet): Kategorie-Fallback-Bps (Sports 75 / Politics-Finance 100 / Crypto 180 / Geopolitics 0 / Default 100) + FeeUsd(notional, bps). - MarketData.TakerFeeBps (Core) + Migration AddMarketTakerFeeBps (auf MySQL angewendet). Speichert den echten API-Satz, sobald verfuegbar; 0 = Fallback. - CopyTradingEngine loggt beim BUY die erwartete Taker-Fee (TakerFeeBps aus MarketCache, sonst Kategorie-Fallback) -> Akzeptanz "Fee im TradeReasoning". - FeeModelTests (Kategorie-Mapping case-insensitive, FeeUsd, 0-Faelle). 183 Tests gruen. Build/Smoke gruen. OFFEN (API-abhaengig, erst im Zielland verifizierbar): echtes fee_rate_bps-Feld aus der CLOB/Gamma-API lesen; Fee-basierter Edge-Discard; PnL-Fee-Korrektur. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
395caad11a
commit
cda5b055aa
@@ -0,0 +1,41 @@
|
||||
namespace PolyTrader.Modules.CopyTrading.Logic
|
||||
{
|
||||
/// <summary>
|
||||
/// Reine Fee-Logik (Phase 0.2). Polymarket erhebt seit März 2026 Taker-Fees (Maker zahlen 0).
|
||||
/// Der reale Satz kommt idealerweise vom Markt-Objekt der CLOB-/Gamma-API (Feldname im Zielland
|
||||
/// anhand https://docs.polymarket.com/trading/fees verifizieren); bis dahin greift die
|
||||
/// Kategorie-Fallback-Tabelle. Werte in Basispunkten (bps): 100 bps = 1,0 %.
|
||||
/// </summary>
|
||||
public static class FeeModel
|
||||
{
|
||||
public const int MakerBps = 0;
|
||||
|
||||
// Fallback-Sätze je Kategorie (Taker, bps) – Näherung laut Plan, im Zielland verifizieren.
|
||||
public const int SportsBps = 75; // 0,75 %
|
||||
public const int PoliticsFinanceBps = 100; // 1,0 %
|
||||
public const int CryptoBps = 180; // 1,8 %
|
||||
public const int GeopoliticsBps = 0; // 0 %
|
||||
public const int DefaultBps = 100; // konservativer Default
|
||||
|
||||
/// <summary>
|
||||
/// Fallback-Taker-Fee (bps) anhand der Markt-Kategorie, wenn die API keinen Satz liefert.
|
||||
/// Robust gegen unbekannte/leere Kategorien (→ konservativer Default).
|
||||
/// </summary>
|
||||
public static int FallbackBps(string? category)
|
||||
{
|
||||
string c = (category ?? string.Empty).ToLowerInvariant();
|
||||
if (c.Contains("sport")) return SportsBps;
|
||||
if (c.Contains("crypto")) return CryptoBps;
|
||||
if (c.Contains("geopolit")) return GeopoliticsBps;
|
||||
if (c.Contains("politic") || c.Contains("finance") || c.Contains("econom")) return PoliticsFinanceBps;
|
||||
return DefaultBps;
|
||||
}
|
||||
|
||||
/// <summary>Taker-Fee in USDC für ein Notional (Preis × Shares) bei gegebenem Satz.</summary>
|
||||
public static decimal FeeUsd(decimal notionalUsd, int feeBps)
|
||||
{
|
||||
if (feeBps <= 0 || notionalUsd <= 0m) return 0m;
|
||||
return notionalUsd * feeBps / 10000m;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,6 +396,14 @@ namespace PolyTraderSharp.Services
|
||||
|
||||
exactShares = exact.shares;
|
||||
exactUsdc = exact.usdc;
|
||||
|
||||
// Phase 0.2: erwartete Taker-Fee transparent loggen (API-Satz bevorzugt,
|
||||
// sonst Kategorie-Fallback). Wird im Zielland gegen echte API-Fees verifiziert.
|
||||
int feeBps = _state.MarketCache.TryGetValue(signal.TokenId, out var feeMd)
|
||||
? (feeMd.TakerFeeBps > 0 ? feeMd.TakerFeeBps : FeeModel.FallbackBps(feeMd.Category))
|
||||
: FeeModel.FallbackBps(null);
|
||||
decimal expectedFee = FeeModel.FeeUsd(exactUsdc, feeBps);
|
||||
_logger.TradeReasoning($"💸 [FEE] {account.Name} | {signal.MarketQuestion}: erwartete Taker-Fee ~${expectedFee:F4} ({feeBps} bps auf ${exactUsdc:F2}).");
|
||||
}
|
||||
else if (signal.Side == "SELL")
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user