Roadmap 0.1, 1.2 und 1.3: Key raus, Build warnungsfrei, Backfill eingegrenzt
0.1 OpenRouter-Schluessel aus appsettings.json entfernt. Laut Nutzer war er bereits inaktiv, eine Rotation beim Anbieter also nicht mehr noetig. Das Feld bleibt leer stehen, mit Hinweis daneben: der Schluessel gehoert in die Umgebungsvariable OpenRouter__ApiKey, die ohnehin Vorrang hat. Damit die Anwendung ohne Schluessel sauber laeuft, steigt OpenRouterApiClient jetzt frueh aus, statt zu senden. Vorher haette jeder Analyseversuch einen 401 erzeugt - der als Error im Fehler-Stream des Deployment Centers gelandet waere, worauf der Aufrufer die Fehlermeldung als JSON zu parsen versucht und einen zweiten Fehler erzeugt haette. Der Zustand wird einmal als Warning gemeldet. Die Konstante NotConfigured liegt im Interface, weil Application die Infrastructure-Schicht nicht referenziert. 1.2 Alle acht Nullable-Warnungen behoben, der Build ist jetzt warnungsfrei. Es waren durchweg Annotationsfragen, kein Verhalten aendert sich: * TradeRepository (6x) - die Parameterliste nimmt bewusst null auf, damit nullable Spalten als SQL-NULL geschrieben werden. Jetzt List<object?>. * TraderAnalyticsWorker - ThenInclude(o => o!.Market); der Lambda ist ein Ausdrucksbaum fuer EF und laeuft nie. * PolymarketProvider - raw.Question ?? ""; TruncateMarketStrings normalisierte den Wert einen Schritt spaeter ohnehin auf "". 1.3 Der Kategorie-Backfill holte den gesamten Marktbestand samt Event in den Speicher und verwarf den Grossteil sofort wieder. Die Einschraenkung auf Maerkte mit gefuellten Event-Tags steht jetzt in der Abfrage statt in der Schleife. Ergebnis unveraendert. Dabei einen eigenen Fehler in der Roadmap korrigiert: dort stand, dieser Backfill laufe "bei jedem Start". Das stimmt nicht - er gehoert zu RunRecalculateAllTradersAsync, der manuellen Wartungsaktion, die nur auf Knopfdruck laeuft. Die Startzeit war nie betroffen. Die falsche Aussage stand auch in STATUS.md und ist dort ebenfalls richtiggestellt. Build warnungsfrei, 126 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+42
-21
@@ -28,22 +28,28 @@ schon: jede baut auf der vorigen auf.
|
|||||||
|
|
||||||
## Stufe 0 — Sofort
|
## Stufe 0 — Sofort
|
||||||
|
|
||||||
### 0.1 🔴 👤 OpenRouter-API-Key zurückziehen und neu ausstellen
|
### 0.1 ✅ OpenRouter-API-Key aus dem Repository entfernt
|
||||||
|
|
||||||
`src/Predictalytics.Api/appsettings.json` enthält einen **echten** Schlüssel im Klartext,
|
*Erledigt am 2026-08-24.* Der Schlüssel war laut Nutzer bereits inaktiv und wurde deshalb
|
||||||
versioniert seit Commit `7045002` und damit auf dem Gitea-Server.
|
ersatzlos aus `src/Predictalytics.Api/appsettings.json` entfernt — eine Rotation war nicht
|
||||||
|
mehr nötig.
|
||||||
|
|
||||||
1. Schlüssel bei OpenRouter widerrufen, neuen ausstellen.
|
**Für den Ersatz:** den neuen Schlüssel **nicht** in die Datei schreiben, sondern als
|
||||||
2. Neuen Schlüssel **nicht** in die Datei schreiben, sondern als Umgebungsvariable
|
Umgebungsvariable `OpenRouter__ApiKey` setzen. `WebApplication.CreateBuilder` liest
|
||||||
`OpenRouter__ApiKey` setzen. `WebApplication.CreateBuilder` liest Umgebungsvariablen mit
|
Umgebungsvariablen mit Vorrang vor `appsettings.json`, eine Codeänderung braucht es nicht.
|
||||||
Vorrang vor `appsettings.json` — dafür ist keine Codeänderung nötig.
|
Die Datei trägt jetzt einen entsprechenden Hinweis neben dem leeren Feld.
|
||||||
3. Erst danach den Platzhalter in `appsettings.json` leeren.
|
|
||||||
|
|
||||||
Den Wert nur aus der Datei zu löschen genügt nicht: der Git-Verlauf behält ihn. Deshalb
|
Ohne Schlüssel läuft die Anwendung sauber weiter: `OpenRouterApiClient` sendet gar nicht
|
||||||
führt an Schritt 1 kein Weg vorbei.
|
erst und meldet den Zustand einmalig als Warnung. Vorher hätte jeder Analyseversuch einen
|
||||||
|
401 erzeugt, der als Fehler im Deployment Center gelandet wäre und dessen Fehlertext der
|
||||||
|
Aufrufer anschließend als JSON zu lesen versucht hätte.
|
||||||
|
|
||||||
> Herkunft: [`archiv/UMSETZUNGSPLAN.md`](docs/archiv/UMSETZUNGSPLAN.md) B2 — dort noch als „kein akuter Risikofall" eingestuft, was
|
> Der alte Schlüssel bleibt im Git-Verlauf (ab Commit `7045002`). Das ist hier folgenlos,
|
||||||
> sich als falsch herausgestellt hat.
|
> weil er inaktiv ist — bei einem aktiven Schlüssel wäre die Rotation beim Anbieter der
|
||||||
|
> einzige wirksame Schritt gewesen.
|
||||||
|
>
|
||||||
|
> Herkunft: [`archiv/UMSETZUNGSPLAN.md`](docs/archiv/UMSETZUNGSPLAN.md) B2 — dort noch als
|
||||||
|
> „kein akuter Risikofall" eingestuft, was sich als falsch herausgestellt hat.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -62,18 +68,33 @@ integration smart-money-comovement smart-money-graph ui-ranglisten-showcases
|
|||||||
ops-watchdog-license fix-event-description-truncation
|
ops-watchdog-license fix-event-description-truncation
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.2 🟡 Acht Nullable-Warnungen beheben
|
### 1.2 ✅ Nullable-Warnungen behoben — der Build ist warnungsfrei
|
||||||
|
|
||||||
`TradeRepository.cs:94-102` (6×), `PolymarketProvider.cs:357`,
|
*Erledigt am 2026-08-24.* Alle acht waren Annotationsfragen, keine echten Fehler; das
|
||||||
`TraderAnalyticsWorker.cs:152`. Bestehen seit Längerem und sind kein Aufräumen, sondern
|
Laufzeitverhalten ist unverändert:
|
||||||
Logikarbeit — jede will einzeln angesehen werden, ob `null` dort auftreten kann.
|
|
||||||
|
|
||||||
### 1.3 🟡 Startup-Backfill entschärfen
|
* `TradeRepository.cs` (6×) — die Parameterliste nimmt bewusst `null` auf, damit nullable
|
||||||
|
Spalten als SQL-NULL geschrieben werden. Jetzt als `List<object?>` deklariert, statt an
|
||||||
|
sechs Stellen zu unterdrücken.
|
||||||
|
* `TraderAnalyticsWorker.cs:152` — `ThenInclude(o => o!.Market)`. Der Lambda ist ein
|
||||||
|
Ausdrucksbaum für EF und wird nie ausgeführt.
|
||||||
|
* `PolymarketProvider.cs:357` — `raw.Question ?? ""`. Das Feld wurde ohnehin einen Schritt
|
||||||
|
später von `TruncateMarketStrings` auf `""` normalisiert.
|
||||||
|
|
||||||
`PredictalyticsHost.cs:116` lädt bei **jedem Start** alle Märkte samt Event in den
|
**Der Build sollte warnungsfrei bleiben.** Eine neue Warnung fällt nur auf, solange es
|
||||||
Speicher, um Kategorien neu abzuleiten. Bei wachsendem Bestand belastet das die Startzeit
|
keine alten gibt.
|
||||||
spürbar. Entweder auf Märkte mit `Category = Other` einschränken, oder einmalig laufen
|
|
||||||
lassen und über ein Flag abschalten.
|
### 1.3 ✅ Kategorie-Backfill lädt nicht mehr den ganzen Marktbestand
|
||||||
|
|
||||||
|
*Erledigt am 2026-08-24.* Der Backfill in `RunRecalculateAllTradersAsync` holte alle Märkte
|
||||||
|
samt Event in den Speicher und verwarf den Großteil sofort wieder. Die Einschränkung auf
|
||||||
|
Märkte mit gefüllten Event-Tags steht jetzt in der Abfrage statt in der Schleife; das
|
||||||
|
Ergebnis ist unverändert.
|
||||||
|
|
||||||
|
> **Korrektur zur ersten Fassung dieser Roadmap:** dort stand, das laufe „bei jedem Start".
|
||||||
|
> Das war falsch — es ist die manuelle Wartungsaktion *Recalculate All Traders*, die nur
|
||||||
|
> auf Knopfdruck in der Shell läuft. Die Startzeit war nie betroffen, das Speicherrisiko
|
||||||
|
> bei der Aktion selbst schon.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ nicht versehentlich als Aufgabe wieder auftaucht:
|
|||||||
| 8 Nullable-Warnungen | `TradeRepository.cs:94-102`, `PolymarketProvider.cs:357`, `TraderAnalyticsWorker.cs:152`. Bestehen seit Längerem; sie zu beheben ist Logikarbeit, kein Aufräumen. |
|
| 8 Nullable-Warnungen | `TradeRepository.cs:94-102`, `PolymarketProvider.cs:357`, `TraderAnalyticsWorker.cs:152`. Bestehen seit Längerem; sie zu beheben ist Logikarbeit, kein Aufräumen. |
|
||||||
| 65 Migrationsdateien | Die Schemahistorie. Zusammenfassen ginge, ist aber riskant — siehe Roadmap 4.4. |
|
| 65 Migrationsdateien | Die Schemahistorie. Zusammenfassen ginge, ist aber riskant — siehe Roadmap 4.4. |
|
||||||
|
|
||||||
Ein Nebenbefund ohne Handlungszwang: der Kategorie-Backfill beim Start
|
*(Ein hier zuvor notierter Nebenbefund — der Kategorie-Backfill lade beim Start alle Märkte
|
||||||
(`PredictalyticsHost.cs:116`) lädt **alle** Märkte samt Event in den Speicher. Bei
|
in den Speicher — war in zwei Punkten falsch: er läuft nicht beim Start, sondern nur in der
|
||||||
wachsendem Bestand wird das die Startzeit spürbar belasten.
|
manuellen Aktion „Recalculate All Traders", und die Abfrage ist seit dem 2026-08-24
|
||||||
|
eingeschränkt. Siehe Roadmap 1.3.)*
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
},
|
},
|
||||||
"OpenRouter": {
|
"OpenRouter": {
|
||||||
"BaseUrl": "https://openrouter.ai/api/v1",
|
"BaseUrl": "https://openrouter.ai/api/v1",
|
||||||
"ApiKey": "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"
|
"_ApiKey": "NIEMALS hier eintragen - der Schluessel gehoert in die Umgebungsvariable OpenRouter__ApiKey. Diese Datei ist versioniert.",
|
||||||
|
"ApiKey": ""
|
||||||
},
|
},
|
||||||
"Egress": {
|
"Egress": {
|
||||||
"Channels": []
|
"Channels": []
|
||||||
|
|||||||
@@ -5,5 +5,12 @@ namespace Predictalytics.Application.Interfaces;
|
|||||||
|
|
||||||
public interface IOpenRouterApiClient
|
public interface IOpenRouterApiClient
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Rueckgabe, wenn kein API-Schluessel hinterlegt ist. Aufrufer pruefen darauf, statt
|
||||||
|
/// die Antwort als JSON zu lesen - sonst erzeugt eine fehlende Konfiguration bei jedem
|
||||||
|
/// Versuch einen Parse-Fehler im Log.
|
||||||
|
/// </summary>
|
||||||
|
const string NotConfigured = "OpenRouter ist nicht konfiguriert.";
|
||||||
|
|
||||||
Task<string> GenerateChatCompletionAsync(string prompt, bool useManualModel = false, CancellationToken ct = default);
|
Task<string> GenerateChatCompletionAsync(string prompt, bool useManualModel = false, CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,15 @@ public class AiStrategyAnalysisService : IAiStrategyAnalysisService
|
|||||||
_logger.LogInformation("Sending {Model} AI analysis request for trader {TraderName}", manual ? "Manual (Claude)" : "Auto (Gemini)", trader.DisplayName);
|
_logger.LogInformation("Sending {Model} AI analysis request for trader {TraderName}", manual ? "Manual (Claude)" : "Auto (Gemini)", trader.DisplayName);
|
||||||
|
|
||||||
var jsonResult = await _openRouter.GenerateChatCompletionAsync(prompt, manual, ct);
|
var jsonResult = await _openRouter.GenerateChatCompletionAsync(prompt, manual, ct);
|
||||||
|
|
||||||
|
// Ohne Schluessel gar nicht erst parsen: das erzeugte sonst pro Versuch einen
|
||||||
|
// Error-Eintrag, obwohl nur die Konfiguration fehlt.
|
||||||
|
if (jsonResult == IOpenRouterApiClient.NotConfigured)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("KI-Analyse fuer {TraderName} uebersprungen: OpenRouter ist nicht konfiguriert.", trader.DisplayName);
|
||||||
|
return jsonResult;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Remove markdown code blocks if present
|
// Remove markdown code blocks if present
|
||||||
|
|||||||
@@ -111,21 +111,26 @@ public sealed class PredictalyticsHost
|
|||||||
WHERE m.IsResolved = 1 AND (m.ResolutionOutcome IS NULL OR m.ResolutionOutcome = '');", ct);
|
WHERE m.IsResolved = 1 AND (m.ResolutionOutcome IS NULL OR m.ResolutionOutcome = '');", ct);
|
||||||
Log.Information("Backfilled ResolutionOutcome for {Count} markets", backfilled);
|
Log.Information("Backfilled ResolutionOutcome for {Count} markets", backfilled);
|
||||||
|
|
||||||
// 1b. Offline Category/Subcategory backfill:
|
// 1b. Offline Category/Subcategory backfill: Kategorie aus den Event-Tags neu
|
||||||
// Query all markets that have events with tags in the DB, and re-classify them
|
// ableiten, ohne einen einzigen API-Aufruf.
|
||||||
var dbMarkets = await db.Markets.Include(m => m.Event).ToListAsync(ct);
|
//
|
||||||
|
// Die Einschraenkung gehoert in die Abfrage, nicht in die Schleife: ohne sie holt
|
||||||
|
// der Aufruf den gesamten Marktbestand samt Event in den Speicher, um den Grossteil
|
||||||
|
// davon sofort wieder zu verwerfen. Das Ergebnis ist identisch - die Schleife hat
|
||||||
|
// schon vorher nur Maerkte mit gefuellten Event-Tags angefasst.
|
||||||
|
var dbMarkets = await db.Markets
|
||||||
|
.Include(m => m.Event)
|
||||||
|
.Where(m => m.Event != null && m.Event.Tags != null && m.Event.Tags != "")
|
||||||
|
.ToListAsync(ct);
|
||||||
int categoryBackfilledCount = 0;
|
int categoryBackfilledCount = 0;
|
||||||
foreach (var m in dbMarkets)
|
foreach (var m in dbMarkets)
|
||||||
{
|
{
|
||||||
if (m.Event != null && !string.IsNullOrEmpty(m.Event.Tags))
|
var (newCategory, newSubcategory) = Predictalytics.Infrastructure.Helpers.MarketCategoryMapper.Map(string.Empty, m.Event!.Tags, m.Question);
|
||||||
|
if (m.Category != newCategory || m.Subcategory != newSubcategory)
|
||||||
{
|
{
|
||||||
var (newCategory, newSubcategory) = Predictalytics.Infrastructure.Helpers.MarketCategoryMapper.Map(string.Empty, m.Event.Tags, m.Question);
|
m.Category = newCategory;
|
||||||
if (m.Category != newCategory || m.Subcategory != newSubcategory)
|
m.Subcategory = newSubcategory;
|
||||||
{
|
categoryBackfilledCount++;
|
||||||
m.Category = newCategory;
|
|
||||||
m.Subcategory = newSubcategory;
|
|
||||||
categoryBackfilledCount++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (categoryBackfilledCount > 0)
|
if (categoryBackfilledCount > 0)
|
||||||
|
|||||||
@@ -71,7 +71,10 @@ public class TradeRepository : ITradeRepository
|
|||||||
foreach (var chunk in tradeList.Chunk(500))
|
foreach (var chunk in tradeList.Chunk(500))
|
||||||
{
|
{
|
||||||
var sb = new System.Text.StringBuilder("INSERT INTO Trades (PlatformTradeId, MarketId, AssetId, Outcome, Side, Price, Size, Amount, ExecutedAt, TransactionHash, TraderId, MarketOutcomeId, DbMarketId, Platform, IsContextEnriched, AggregatedCount, UsdcSize, OutcomeIndex) VALUES ");
|
var sb = new System.Text.StringBuilder("INSERT INTO Trades (PlatformTradeId, MarketId, AssetId, Outcome, Side, Price, Size, Amount, ExecutedAt, TransactionHash, TraderId, MarketOutcomeId, DbMarketId, Platform, IsContextEnriched, AggregatedCount, UsdcSize, OutcomeIndex) VALUES ");
|
||||||
var parameters = new List<object>();
|
// object? statt object: TransactionHash, MarketOutcomeId, DbMarketId,
|
||||||
|
// AggregatedCount, UsdcSize und OutcomeIndex sind nullable und sollen als
|
||||||
|
// SQL-NULL durchgereicht werden.
|
||||||
|
var parameters = new List<object?>();
|
||||||
|
|
||||||
for (int i = 0; i < chunk.Length; i++)
|
for (int i = 0; i < chunk.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -111,7 +114,7 @@ public class TradeRepository : ITradeRepository
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rowsInserted = await _db.Database.ExecuteSqlRawAsync(sb.ToString(), parameters.ToArray(), ct);
|
var rowsInserted = await _db.Database.ExecuteSqlRawAsync(sb.ToString(), parameters.ToArray()!, ct);
|
||||||
_logger.LogInformation("Inserted {RowsInserted} trades into the database.", rowsInserted);
|
_logger.LogInformation("Inserted {RowsInserted} trades into the database.", rowsInserted);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ public class OpenRouterApiClient : IOpenRouterApiClient
|
|||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private readonly ILogger<OpenRouterApiClient> _logger;
|
private readonly ILogger<OpenRouterApiClient> _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ohne Schluessel wird gar nicht erst gesendet. Sonst quittiert OpenRouter jeden
|
||||||
|
/// Aufruf mit 401, das landet als Error im Fehler-Stream des Deployment Centers und
|
||||||
|
/// der Aufrufer versucht anschliessend, die Fehlermeldung als JSON zu lesen.
|
||||||
|
/// </summary>
|
||||||
|
private readonly bool _configured;
|
||||||
|
|
||||||
public OpenRouterApiClient(HttpClient httpClient, IConfiguration config, ILogger<OpenRouterApiClient> logger)
|
public OpenRouterApiClient(HttpClient httpClient, IConfiguration config, ILogger<OpenRouterApiClient> logger)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
@@ -28,10 +35,17 @@ public class OpenRouterApiClient : IOpenRouterApiClient
|
|||||||
var apiKey = _config["OpenRouter:ApiKey"];
|
var apiKey = _config["OpenRouter:ApiKey"];
|
||||||
|
|
||||||
_httpClient.BaseAddress = new Uri(baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/");
|
_httpClient.BaseAddress = new Uri(baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/");
|
||||||
if (!string.IsNullOrEmpty(apiKey))
|
_configured = !string.IsNullOrWhiteSpace(apiKey);
|
||||||
|
if (_configured)
|
||||||
{
|
{
|
||||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Warning, nicht Error: eine fehlende KI-Anbindung ist ein Konfigurationszustand,
|
||||||
|
// kein Stoerfall - die uebrige Analyse laeuft vollstaendig ohne sie.
|
||||||
|
_logger.LogWarning("OpenRouter ist nicht konfiguriert (OpenRouter:ApiKey leer) - die KI-Strategieanalyse bleibt aus. Schluessel ueber die Umgebungsvariable OpenRouter__ApiKey setzen.");
|
||||||
|
}
|
||||||
// OpenRouter recommends adding a referer and title for ranking
|
// OpenRouter recommends adding a referer and title for ranking
|
||||||
_httpClient.DefaultRequestHeaders.Add("HTTP-Referer", "http://localhost");
|
_httpClient.DefaultRequestHeaders.Add("HTTP-Referer", "http://localhost");
|
||||||
_httpClient.DefaultRequestHeaders.Add("X-Title", "Predictalytics");
|
_httpClient.DefaultRequestHeaders.Add("X-Title", "Predictalytics");
|
||||||
@@ -39,6 +53,8 @@ public class OpenRouterApiClient : IOpenRouterApiClient
|
|||||||
|
|
||||||
public async Task<string> GenerateChatCompletionAsync(string prompt, bool useManualModel = false, CancellationToken ct = default)
|
public async Task<string> GenerateChatCompletionAsync(string prompt, bool useManualModel = false, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
if (!_configured) return IOpenRouterApiClient.NotConfigured;
|
||||||
|
|
||||||
var model = useManualModel
|
var model = useManualModel
|
||||||
? _config["OpenRouter:ManualAnalysisModel"] ?? "anthropic/claude-3-opus"
|
? _config["OpenRouter:ManualAnalysisModel"] ?? "anthropic/claude-3-opus"
|
||||||
: _config["OpenRouter:DefaultModel"] ?? "google/gemini-flash-1.5";
|
: _config["OpenRouter:DefaultModel"] ?? "google/gemini-flash-1.5";
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ public class PolymarketProvider : IPlatformProvider
|
|||||||
MarketSlug = raw.Slug,
|
MarketSlug = raw.Slug,
|
||||||
Description = raw.Description,
|
Description = raw.Description,
|
||||||
ImageUrl = raw.Image,
|
ImageUrl = raw.Image,
|
||||||
Question = raw.Question,
|
Question = raw.Question ?? "",
|
||||||
Category = catMap.Category,
|
Category = catMap.Category,
|
||||||
Subcategory = catMap.Subcategory,
|
Subcategory = catMap.Subcategory,
|
||||||
Volume = (decimal)raw.Volume,
|
Volume = (decimal)raw.Volume,
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ public class TraderAnalyticsWorker : BackgroundService
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var trades = await db.Trades
|
var trades = await db.Trades
|
||||||
.Include(t => t.MarketOutcome).ThenInclude(o => o.Market)
|
.Include(t => t.MarketOutcome).ThenInclude(o => o!.Market)
|
||||||
.Include(t => t.DbMarket)
|
.Include(t => t.DbMarket)
|
||||||
.Include(t => t.Context)
|
.Include(t => t.Context)
|
||||||
.Where(t => t.TraderId == id && t.DbMarketId != null)
|
.Where(t => t.TraderId == id && t.DbMarketId != null)
|
||||||
|
|||||||
Reference in New Issue
Block a user