Fix: Implement FixPlan part A9, A10, B

This commit is contained in:
Richard
2026-07-10 12:04:27 +02:00
parent 44f48284a2
commit 1787422243
28 changed files with 1025 additions and 79 deletions
@@ -46,6 +46,39 @@ public class PolymarketApiClient
return await ExecuteWithRetryAsync<List<PolymarketTradeResponse>>(_client, url, "Data", ct) ?? [];
}
public async Task<List<PolymarketTradeResponse>> GetTradesPagedAsync(string walletAddress, int limit = 500, CancellationToken ct = default)
{
var allTrades = new List<PolymarketTradeResponse>();
long? endTimestamp = null;
while (true)
{
var url = $"/activity?user={walletAddress}&limit={limit}";
if (endTimestamp.HasValue)
{
url += $"&end={endTimestamp.Value}";
}
var batch = await ExecuteWithRetryAsync<List<PolymarketTradeResponse>>(_client, url, "Data", ct);
if (batch == null || batch.Count == 0)
{
break;
}
allTrades.AddRange(batch);
if (batch.Count < limit)
{
break;
}
var oldestTimestamp = batch.Min(t => t.Timestamp);
endTimestamp = oldestTimestamp - 1;
}
return allTrades;
}
public async Task<List<PolymarketTradeResponse>> GetMarketTradesAsync(string conditionId, int limit = 1000, int offset = 0, CancellationToken ct = default)
{
var url = $"/trades?condition_id={conditionId}&limit={limit}&offset={offset}";
@@ -59,10 +59,20 @@ public class PolymarketTradeResponse
[JsonConverter(typeof(FlexibleDoubleConverter))]
public double Size { get; set; }
[JsonPropertyName("usdcSize")]
[JsonConverter(typeof(FlexibleDoubleConverter))]
public double UsdcSize { get; set; }
[JsonPropertyName("price")]
[JsonConverter(typeof(FlexibleDoubleConverter))]
public double Price { get; set; }
[JsonPropertyName("outcomeIndex")]
public int? OutcomeIndex { get; set; }
[JsonPropertyName("name")] public string? Name { get; set; }
[JsonPropertyName("pseudonym")] public string? Pseudonym { get; set; }
[JsonPropertyName("outcome")] public string Outcome { get; set; } = "";
[JsonPropertyName("timestamp")]
@@ -137,7 +147,7 @@ public class GammaMarketResponse
[JsonPropertyName("closed")] public bool Closed { get; set; }
[JsonPropertyName("active")] public bool Active { get; set; }
[JsonPropertyName("resolved")] public bool Resolved { get; set; }
[JsonPropertyName("resolution_outcome")] public string? ResolutionOutcome { get; set; }
[JsonPropertyName("umaResolutionStatus")] public string? UmaResolutionStatus { get; set; }
[JsonPropertyName("negRisk")] public bool NegRisk { get; set; }
[JsonPropertyName("closedTime")] public string? ClosedTime { get; set; }
[JsonPropertyName("takerFee")] [JsonConverter(typeof(FlexibleDoubleConverter))] public double TakerFee { get; set; }
@@ -56,13 +56,50 @@ public class PolymarketProvider : IPlatformProvider
TransactionHash = r.TransactionHash?.ToLowerInvariant(),
TraderId = 0,
TransientWallet = wallet,
TransientDisplayName = !string.IsNullOrEmpty(r.Name) ? r.Name : r.Pseudonym
};
}).ToList();
return mappedTrades.GroupBy(t => t.PlatformTradeId, StringComparer.OrdinalIgnoreCase).Select(g => g.First()).ToList();
}
public async Task<IReadOnlyList<Trade>> GetTradesPagedAsync(string platformUserId, int limitPerRequest = 500, CancellationToken ct = default)
{
using var _ = PlatformLogContext.Push(PlatformName);
_logger.LogDebug("Fetching all paged trades for {Wallet}", platformUserId);
var raw = await _api.GetTradesPagedAsync(platformUserId, limitPerRequest, ct);
_logger.LogInformation("Fetched {Count} trades total for {Wallet}", raw.Count, platformUserId);
var mappedTrades = raw.Select(r =>
{
var wallet = !string.IsNullOrEmpty(r.User) ? r.User :
!string.IsNullOrEmpty(r.ProxyWallet) ? r.ProxyWallet :
platformUserId;
var side = MapTradeSide(r);
var sideStr = side.ToString().ToUpperInvariant();
return new Trade
{
Platform = PlatformType.Polymarket,
PlatformTradeId = string.IsNullOrEmpty(r.TransactionHash)
? $"{r.Timestamp}_{wallet}_{r.Asset}_{sideStr}"
: $"{r.TransactionHash.ToLowerInvariant()}_{wallet}_{r.Asset}_{sideStr}",
MarketId = r.ConditionId ?? "",
AssetId = r.Asset ?? "",
Outcome = r.Outcome ?? "",
Side = side,
Price = (decimal)r.Price,
Size = (decimal)r.Size,
Amount = (decimal)(r.Price * r.Size),
ExecutedAt = DateTimeOffset.FromUnixTimeSeconds(r.Timestamp).UtcDateTime,
TransactionHash = r.TransactionHash?.ToLowerInvariant(),
TraderId = 0,
TransientWallet = wallet,
TransientDisplayName = !string.IsNullOrEmpty(r.Name) ? r.Name : r.Pseudonym
};
}).ToList();
return mappedTrades.GroupBy(t => t.PlatformTradeId, StringComparer.OrdinalIgnoreCase).Select(g => g.First()).ToList();
}
public async Task<IReadOnlyList<Trade>> GetMarketTradesAsync(string platformMarketId, int limit = 1000, CancellationToken ct = default)
{
var raw = await _api.GetMarketTradesAsync(platformMarketId, limit, 0, ct);
@@ -90,6 +127,7 @@ public class PolymarketProvider : IPlatformProvider
TransactionHash = r.TransactionHash,
TraderId = 0,
TransientWallet = wallet,
TransientDisplayName = !string.IsNullOrEmpty(r.Name) ? r.Name : r.Pseudonym
};
}).ToList();
@@ -106,7 +144,8 @@ public class PolymarketProvider : IPlatformProvider
return raw.Select(r => new TraderPositionInfo(
platformUserId, r.Market, r.Question, r.Outcome,
(decimal)r.Size, (decimal)r.AvgPrice,
(decimal)r.CurrentValue, (decimal)r.PercentPnl
(decimal)r.CurrentValue, (decimal)r.PercentPnl,
r.AssetId
)).ToList();
}
@@ -300,8 +339,6 @@ public class PolymarketProvider : IPlatformProvider
EndDate = DateTime.TryParse(raw.EndDate ?? raw.EndDateIso, out var med) ? med : null,
CreatedAt = DateTime.TryParse(raw.CreatedAt, out var mcd) ? mcd : DateTime.UtcNow,
DbCreatedAt = DateTime.UtcNow,
IsResolved = raw.Resolved || raw.Closed,
ResolutionOutcome = raw.ResolutionOutcome,
LastUpdatedAt = DateTime.UtcNow,
FeeRateBps = (decimal)(raw.TakerFee * 10000),
IsNegRisk = raw.NegRisk,
@@ -313,6 +350,11 @@ public class PolymarketProvider : IPlatformProvider
var outcomePrices = ParseJsonStringArray(raw.OutcomePrices);
var tokenIds = ParseJsonStringArray(raw.ClobTokenIds);
bool pricesSnapped = false;
bool hasHighPrice = false;
bool allSnapped = outcomePrices.Count > 0;
string? snappedWinnerLabel = null;
for (int i = 0; i < outcomeLabels.Count; i++)
{
decimal price = 0;
@@ -320,8 +362,6 @@ public class PolymarketProvider : IPlatformProvider
decimal.TryParse(outcomePrices[i], System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out price);
string tokenId = i < tokenIds.Count ? tokenIds[i] : "";
var label = outcomeLabels[i];
if ((label.Equals("Yes", StringComparison.OrdinalIgnoreCase) || label.Equals("No", StringComparison.OrdinalIgnoreCase))
&& !string.IsNullOrEmpty(raw.GroupItemTitle))
@@ -329,6 +369,21 @@ public class PolymarketProvider : IPlatformProvider
label = $"{raw.GroupItemTitle} - {label}";
}
if (price <= 0.02m || price >= 0.98m)
{
if (price >= 0.98m)
{
hasHighPrice = true;
snappedWinnerLabel = label;
}
}
else
{
allSnapped = false;
}
string tokenId = i < tokenIds.Count ? tokenIds[i] : "";
market.Outcomes.Add(new MarketOutcome
{
Label = label,
@@ -338,6 +393,11 @@ public class PolymarketProvider : IPlatformProvider
});
}
pricesSnapped = allSnapped && hasHighPrice;
market.IsResolved = raw.UmaResolutionStatus == "resolved" || (raw.Closed && pricesSnapped);
market.ResolutionOutcome = market.IsResolved ? snappedWinnerLabel : null;
return market;
}