Enhance UI, add AI integration, improve logging and database stats

This commit is contained in:
Richard
2026-07-04 21:11:31 +02:00
parent 7a44914d9d
commit d102af2965
57 changed files with 4025 additions and 229 deletions
@@ -32,17 +32,19 @@ public class PolymarketProvider : IPlatformProvider
var mappedTrades = raw.Select(r =>
{
var wallet = r.User ?? r.ProxyWallet ?? "";
var wallet = !string.IsNullOrEmpty(r.User) ? r.User :
!string.IsNullOrEmpty(r.ProxyWallet) ? r.ProxyWallet :
platformUserId;
var side = MapTradeSide(r);
var sideStr = side.ToString().ToUpperInvariant();
// Compact format: {txHash}_{assetId}_{side} — no wallet in ID to reduce index size.
// Wallet passed transiently via TransientWallet [NotMapped] for MarketHistoryWorker.
// Format: {txHash}_{wallet}_{assetId}_{side}
// Wallet must be included to avoid cross-user collisions in the global IX_Trades_Platform_PlatformTradeId index.
return new Trade
{
Platform = PlatformType.Polymarket,
PlatformTradeId = string.IsNullOrEmpty(r.TransactionHash)
? $"{r.Timestamp}_{r.Asset}_{sideStr}"
: $"{r.TransactionHash}_{r.Asset}_{sideStr}",
? $"{r.Timestamp}_{wallet}_{r.Asset}_{sideStr}"
: $"{r.TransactionHash.ToLowerInvariant()}_{wallet}_{r.Asset}_{sideStr}",
MarketId = r.ConditionId ?? "",
AssetId = r.Asset ?? "",
Outcome = r.Outcome ?? "",
@@ -51,13 +53,13 @@ public class PolymarketProvider : IPlatformProvider
Size = (decimal)r.Size,
Amount = (decimal)(r.Price * r.Size),
ExecutedAt = DateTimeOffset.FromUnixTimeSeconds(r.Timestamp).UtcDateTime,
TransactionHash = r.TransactionHash,
TransactionHash = r.TransactionHash?.ToLowerInvariant(),
TraderId = 0,
TransientWallet = wallet,
};
}).ToList();
return mappedTrades.GroupBy(t => t.PlatformTradeId).Select(g => g.First()).ToList();
return mappedTrades.GroupBy(t => t.PlatformTradeId, StringComparer.OrdinalIgnoreCase).Select(g => g.First()).ToList();
}
@@ -75,8 +77,8 @@ public class PolymarketProvider : IPlatformProvider
{
Platform = PlatformType.Polymarket,
PlatformTradeId = string.IsNullOrEmpty(r.TransactionHash)
? $"{r.Timestamp}_{r.Asset}_{sideStr}"
: $"{r.TransactionHash}_{r.Asset}_{sideStr}",
? $"{r.Timestamp}_{wallet}_{r.Asset}_{sideStr}"
: $"{r.TransactionHash.ToLowerInvariant()}_{wallet}_{r.Asset}_{sideStr}",
MarketId = r.ConditionId ?? "",
AssetId = r.Asset ?? "",
Outcome = r.Outcome ?? "",
@@ -127,33 +129,76 @@ public class PolymarketProvider : IPlatformProvider
public async Task<Market?> GetMarketAsync(string platformMarketId, CancellationToken ct = default)
{
using var _ = PlatformLogContext.Push(PlatformName);
_logger.LogDebug("Fetching market {MarketId}", platformMarketId);
var raw = await _api.GetMarketAsync(platformMarketId, ct);
if (raw == null)
if (raw == null || string.IsNullOrEmpty(raw.ConditionId)) return null;
var parentTags = "";
if (raw.Events != null && raw.Events.Count > 0)
{
_logger.LogWarning("Market {MarketId} not found", platformMarketId);
return null;
var ev = raw.Events[0];
parentTags = ev.Tags != null ? string.Join(", ", ev.Tags.Select(t => t.Label)) : "";
}
var market = MapGammaMarket(raw, parentTags);
// Map the parent Event if available in the Market response
if (raw.Events != null && raw.Events.Count > 0)
{
var rawEv = raw.Events[0];
long.TryParse(rawEv.Id, out var numericEventId);
market.Event = new Event
{
Platform = PlatformType.Polymarket,
PlatformEventId = numericEventId,
Slug = rawEv.Slug,
Title = rawEv.Title,
Description = rawEv.Description,
ImageUrl = rawEv.Image,
StartDate = DateTime.TryParse(rawEv.StartDate, out var esd) ? esd : null,
EndDate = DateTime.TryParse(rawEv.EndDate, out var eed) ? eed : null,
CreatedAt = DateTime.TryParse(rawEv.CreatedAt, out var ecd) ? ecd : DateTime.UtcNow,
DbCreatedAt = DateTime.UtcNow,
IsActive = rawEv.Active,
IsClosed = rawEv.Closed,
Tags = rawEv.Tags != null && rawEv.Tags.Count > 0 ? string.Join(", ", rawEv.Tags.Select(t => t.Label)) : string.Empty,
LastUpdatedAt = DateTime.UtcNow
};
}
else
{
// Fallback empty event if missing (should rarely happen for valid Polymarket markets)
market.Event = new Event
{
Platform = PlatformType.Polymarket,
Slug = "unknown-" + market.ConditionId,
Title = "Unknown Event",
DbCreatedAt = DateTime.UtcNow,
LastUpdatedAt = DateTime.UtcNow
};
}
_logger.LogInformation("Fetched market: {Question}", raw.Question);
return MapGammaMarket(raw);
return market;
}
public async Task<IReadOnlyList<Market>> GetMarketsAsync(int limit = 100, string? cursor = null, bool includeClosed = false, CancellationToken ct = default)
public async Task<IReadOnlyList<Event>> GetEventsAsync(int limit = 100, string? cursor = null, bool includeClosed = false, CancellationToken ct = default)
{
using var _ = PlatformLogContext.Push(PlatformName);
int offset = 0;
if (!string.IsNullOrEmpty(cursor) && int.TryParse(cursor, out var parsed))
offset = parsed;
_logger.LogInformation("Fetching markets batch (limit={Limit}, offset={Offset}, includeClosed={Closed})", limit, offset, includeClosed);
var raw = await _api.GetMarketsAsync(limit, offset, includeClosed, ct);
_logger.LogInformation("Fetched {Count} markets from Gamma API", raw.Count);
_logger.LogInformation("Fetching events batch (limit={Limit}, offset={Offset}, includeClosed={Closed})", limit, offset, includeClosed);
var rawEvents = await _api.GetEventsAsync(limit, offset, includeClosed, ct);
_logger.LogInformation("Fetched {Count} events from Gamma API", rawEvents.Count);
return raw
.Where(m => !string.IsNullOrEmpty(m.ConditionId) && !string.IsNullOrEmpty(m.ClobTokenIds))
.Select(MapGammaMarket)
.ToList();
var events = new List<Event>();
foreach (var rawEvent in rawEvents)
{
var ev = MapGammaEvent(rawEvent);
events.Add(ev);
}
return events;
}
public async Task<IReadOnlyList<DiscoveredTrader>> GetTopHoldersAsync(string platformMarketId, int limit = 20, CancellationToken ct = default)
@@ -192,31 +237,67 @@ public class PolymarketProvider : IPlatformProvider
// ── Private helpers ──────────────────────────────────────────
private Market MapGammaMarket(GammaMarketResponse raw)
private Event MapGammaEvent(GammaEventResponse rawEvent)
{
var eventSlug = "";
if (raw.Events != null && raw.Events.Count > 0 && !string.IsNullOrEmpty(raw.Events[0].Slug))
long.TryParse(rawEvent.Id, out var numericId);
var ev = new Event
{
eventSlug = raw.Events[0].Slug;
Platform = PlatformType.Polymarket,
PlatformEventId = numericId,
Slug = rawEvent.Slug,
Title = rawEvent.Title,
Description = rawEvent.Description,
ImageUrl = rawEvent.Image,
StartDate = DateTime.TryParse(rawEvent.StartDate, out var sd) ? sd : null,
EndDate = DateTime.TryParse(rawEvent.EndDate, out var ed) ? ed : null,
CreatedAt = DateTime.TryParse(rawEvent.CreatedAt, out var cd) ? cd : DateTime.UtcNow,
DbCreatedAt = DateTime.UtcNow,
IsActive = rawEvent.Active,
IsClosed = rawEvent.Closed,
Tags = rawEvent.Tags != null && rawEvent.Tags.Count > 0
? string.Join(", ", rawEvent.Tags.Select(t => t.Label))
: string.Empty,
LastUpdatedAt = DateTime.UtcNow
};
if (rawEvent.Markets != null)
{
foreach (var rawMarket in rawEvent.Markets)
{
if (string.IsNullOrEmpty(rawMarket.ConditionId) || string.IsNullOrEmpty(rawMarket.ClobTokenIds)) continue;
var market = MapGammaMarket(rawMarket, ev.Tags ?? "");
ev.Markets.Add(market);
}
}
return ev;
}
private Market MapGammaMarket(GammaMarketResponse raw, string parentTags = "")
{
long.TryParse(raw.Id, out var marketNumericId);
var market = new Market
{
Platform = PlatformType.Polymarket,
PlatformMarketId = raw.ConditionId,
PlatformMarketId = marketNumericId,
ConditionId = raw.ConditionId,
QuestionId = raw.QuestionId,
MarketSlug = raw.Slug,
EventSlug = eventSlug,
Description = raw.Description,
ImageUrl = raw.Image,
Question = raw.Question,
Category = raw.Category,
Category = string.IsNullOrWhiteSpace(raw.Category) ? parentTags : raw.Category,
Volume = (decimal)raw.Volume,
Volume24h = (decimal)raw.Volume24hr,
Liquidity = (decimal)raw.Liquidity,
StartDate = DateTime.TryParse(raw.StartDate, out var sd) ? sd : null,
EndDate = DateTime.TryParse(raw.EndDate, out var ed) ? ed : null,
CreatedAt = DateTime.TryParse(raw.CreatedAt, out var cd) ? cd : DateTime.UtcNow,
StartDate = DateTime.TryParse(raw.StartDate, out var msd) ? msd : null,
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, // Prefer resolved flag
IsResolved = raw.Resolved || raw.Closed,
ResolutionOutcome = raw.ResolutionOutcome,
LastUpdatedAt = DateTime.UtcNow
};