Fruehjahrsputz 1/2: toter Code, Altlast-Dateien, veraltete Verweise
Toter Code: * ApiConfiguration.ConfigureApi (29 Z.) hatte keinen Aufrufer mehr. PredictalyticsHost baut die WebApplication seit Phase 2 selbst auf (Kestrel, CORS, Swagger, Static Files); ConfigureApi war der zurueckgebliebene Zwilling aus der Zeit davor. * IAnalyticsService.GetTraitsAsync samt Implementierung. Die Methode gab konstant eine leere Liste zurueck; ihr eigener Kommentar hielt fest, dass sie ungenutzt ist. Der Endpunkt /api/traders/traits liest direkt aus dem DbContext und bleibt unveraendert. Ungenutzte Paketreferenzen: * Swashbuckle.AspNetCore aus Predictalytics.Api - wurde nur von ConfigureApi gebraucht; Swagger baut Hosting auf, das die Referenz selbst haelt. * Microsoft.EntityFrameworkCore.Design aus Predictalytics.Worker - die Design-Time-Factory liegt in Infrastructure. * Serilog.Sinks.File/.Console und Serilog.Formatting.Compact aus Predictalytics.Infrastructure - dort wird kein Logger konfiguriert, nur Serilog.Core/Events/Context verwendet. Die Sinks haengen am Hosting. Altlast-Dateien: * Spike/ - Projektdatei ohne eine einzige Quelldatei, net8.0, nicht in der Solution. * query.csx - Ad-hoc-Abfrageskript von Juli mit fest eingetragenen DB-Zugangsdaten. * NewDesign.zip (388 KB) und temp_new_design/ - Rohmaterial des Design-Entwurfs vom 15.07. Das Ergebnis liegt fertig in wwwroot/landing.html und wwwroot/docs.html. Veraltete Verweise auf den entfernten WinForms-Host: * CLAUDE.md nannte fuer die Release-Version eine csproj, die es nicht mehr gibt - sie steht seit der Zentralisierung in Directory.Build.props. * docs/BETRIEB-Deploymentcenter.md: Anbindung und BuildInfo sitzen in Predictalytics.Hosting. * docs/API.md: Die Aussage "ohne Authentifizierung" galt vor Phase 4. Jetzt mit Header X-Predictalytics-Key und den heutigen Methodennamen. * Kommentare in Directory.Build.props, DevEndpoints.cs, PredictalyticsHost.cs. Build ohne neue Warnungen (die 8 bestehenden CS86xx sind unveraendert), 126 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -101,9 +101,8 @@ Body: {"product_slug": "predictalytics", "version": "1.0.1",
|
|||||||
"download_url": "...", "sha256_hash": "...", "git_commit": "..."}
|
"download_url": "...", "sha256_hash": "...", "git_commit": "..."}
|
||||||
```
|
```
|
||||||
|
|
||||||
Die Version in `src/Predictalytics.WinFormsHost/Predictalytics.WinFormsHost.csproj`
|
Die Version in `Directory.Build.props` (`<Version>`) muss dazu passen — sie ist
|
||||||
(`<Version>`) muss dazu passen — sie ist es, die der Client als installierte
|
es, die der Client als installierte Version meldet.
|
||||||
Version meldet.
|
|
||||||
|
|
||||||
### Fehlerbehandlung
|
### Fehlerbehandlung
|
||||||
Antworten haben die Form `{"status":"error","error":{"code":"…"}}`.
|
Antworten haben die Form `{"status":"error","error":{"code":"…"}}`.
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<!-- Version der Anwendung. Wird von Deploymentcenter.BuildInfo.targets,
|
<!-- Version der Anwendung. Wird von Deploymentcenter.BuildInfo.targets,
|
||||||
dem UpdateService, Fehler-Stream und Heartbeat genutzt. -->
|
dem UpdateService, Fehler-Stream und Heartbeat genutzt. -->
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<!-- Zielframework zentral. Ausnahme: Predictalytics.WinFormsHost setzt
|
<!-- Zielframework zentral fuer alle Projekte. Seit die Avalonia-Shell den
|
||||||
net10.0-windows, weil WinForms das Windows-Desktop-Pack braucht. -->
|
WinForms-Host abgeloest hat, gibt es keine windows-spezifische Ausnahme mehr. -->
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|||||||
Binary file not shown.
@@ -1,8 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
+9
-7
@@ -1,10 +1,12 @@
|
|||||||
# Predictalytics REST API — Referenz
|
# Predictalytics REST API — Referenz
|
||||||
|
|
||||||
> Stand: 2026-07-11 · Interne API ohne Authentifizierung (nur lokal betreiben!).
|
> Stand: 2026-08-23 · Interne API, nur lokal betreiben.
|
||||||
> Interaktive Doku: **`/swagger`** auf dem laufenden Server (Standalone-API und
|
> **Lesende** Endpunkte sind ungeschützt, **steuernde** verlangen seit Phase 4 den
|
||||||
> WinForms-Embedded-Server registrieren identische Endpoints über
|
> Header `X-Predictalytics-Key`, sobald in den Einstellungen ein Token hinterlegt ist
|
||||||
> `ApiConfiguration.MapPredictalyticsEndpoints()` — dort ist die einzige Stelle,
|
> (siehe `ApiTokenFilter`; `GET /api/capabilities` meldet der WebUI den Ist-Zustand).
|
||||||
> an der neue Endpoints registriert werden dürfen).
|
> Interaktive Doku: **`/swagger`** auf dem laufenden Server. Endpunkte werden
|
||||||
|
> ausschliesslich in `ApiConfiguration.MapPredictalyticsReadEndpoints()` bzw.
|
||||||
|
> `MapPredictalyticsControlEndpoints()` registriert — nur dort neue ergänzen.
|
||||||
|
|
||||||
Konventionen: JSON mit camelCase-Feldnamen · Zeiten in UTC (ISO 8601) ·
|
Konventionen: JSON mit camelCase-Feldnamen · Zeiten in UTC (ISO 8601) ·
|
||||||
Prozente auf 0–100-Skala · Beträge in USD · `WinRate` ist marktbasiert
|
Prozente auf 0–100-Skala · Beträge in USD · `WinRate` ist marktbasiert
|
||||||
@@ -140,8 +142,8 @@ Jobs werden von den Workern abgearbeitet (Worker müssen laufen!). Status:
|
|||||||
| `GET /api/dev/verify-positions/{traderId}` | Vergleicht unsere `TraderPositions` mit Polymarkets `/positions`-API; Antwort: `mismatches[]`, `match`, `dbPositionsCount`, `apiPositionsCount` |
|
| `GET /api/dev/verify-positions/{traderId}` | Vergleicht unsere `TraderPositions` mit Polymarkets `/positions`-API; Antwort: `mismatches[]`, `match`, `dbPositionsCount`, `apiPositionsCount` |
|
||||||
|
|
||||||
> Ein früherer `POST /api/dev/repair-db` wurde entfernt (fehlerhaftes SQL,
|
> Ein früherer `POST /api/dev/repair-db` wurde entfernt (fehlerhaftes SQL,
|
||||||
> destruktiv). Reparatur läuft über den WinForms-Menüpunkt **Development →
|
> destruktiv). Reparatur läuft über die Schaltfläche **Recalculate All Traders**
|
||||||
> Recalculate All Traders**.
|
> in der Avalonia-Shell.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Das **Deployment Center** (`https://dc.mhdf.de`, Schwester-Repo
|
|||||||
| LicenseLabrador auf `license.mhdf.de`, Ed25519-signierte Envelopes | Deployment Center, `POST /api/license/v1/validate`, Hardware-ID v2 |
|
| LicenseLabrador auf `license.mhdf.de`, Ed25519-signierte Envelopes | Deployment Center, `POST /api/license/v1/validate`, Hardware-ID v2 |
|
||||||
| — | UpdateService, Fehler-Stream und Bugtracker (neu) |
|
| — | UpdateService, Fehler-Stream und Bugtracker (neu) |
|
||||||
|
|
||||||
Die Anbindung sitzt im `Predictalytics.WinFormsHost` und referenziert das
|
Die Anbindung sitzt in `Predictalytics.Hosting` und referenziert das
|
||||||
C#-SDK `Deploymentcenter.Client` aus dem Schwester-Repo:
|
C#-SDK `Deploymentcenter.Client` aus dem Schwester-Repo:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -209,10 +209,10 @@ Release Notes also auch dann, wenn die statische `latest.json` fehlt.
|
|||||||
|
|
||||||
### Versionsstand
|
### Versionsstand
|
||||||
|
|
||||||
`Predictalytics.WinFormsHost.csproj` importiert `Deploymentcenter.BuildInfo.targets`
|
`Predictalytics.Hosting.csproj` importiert `Deploymentcenter.BuildInfo.targets`
|
||||||
und erzeugt daraus zur Übersetzungszeit `Predictalytics.WinFormsHost.BuildInfo`
|
und erzeugt daraus zur Übersetzungszeit `Predictalytics.Hosting.BuildInfo`
|
||||||
mit `Version`, `GitCommit`, `GitCommitShort`, `BuildDateUtc`, `Channel` und
|
mit `Version`, `GitCommit`, `GitCommitShort`, `BuildDateUtc`, `Channel` und
|
||||||
`Summary`. Quelle ist `<Version>` in derselben csproj — beim Release dort
|
`Summary`. Quelle ist `<Version>` in `Directory.Build.props` — beim Release dort
|
||||||
hochziehen und mit
|
hochziehen und mit
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,49 +1,18 @@
|
|||||||
using Predictalytics.Api.Endpoints;
|
using Predictalytics.Api.Endpoints;
|
||||||
using Predictalytics.Api.Security;
|
using Predictalytics.Api.Security;
|
||||||
using Predictalytics.Infrastructure;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Predictalytics.Api;
|
namespace Predictalytics.Api;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the API WebApplication. Used by the embedded Kestrel server.
|
/// Registriert die Endpunkte der lokalen API. Den WebApplication-Aufbau selbst
|
||||||
|
/// (Kestrel, CORS, Swagger, Static Files) macht Predictalytics.Hosting —
|
||||||
|
/// hier stehen nur die Routen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ApiConfiguration
|
public static class ApiConfiguration
|
||||||
{
|
{
|
||||||
public static WebApplication ConfigureApi(WebApplicationBuilder builder, string? connectionStringOverride = null)
|
|
||||||
{
|
|
||||||
builder.Services.AddPredictalytics(builder.Configuration, connectionStringOverride);
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1",
|
|
||||||
new() { Title = "Predictalytics Analytics API", Version = "v1" }));
|
|
||||||
var allowedOrigins = builder.Configuration.GetSection("ApiSettings:AllowedOrigins").Get<string[]>()
|
|
||||||
?? new[] { "http://localhost:5000" };
|
|
||||||
builder.Services.AddCors(o => o.AddDefaultPolicy(p =>
|
|
||||||
p.WithOrigins(allowedOrigins).AllowAnyMethod().AllowAnyHeader()));
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
app.UseCors();
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI();
|
|
||||||
app.UseDefaultFiles();
|
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
|
||||||
{
|
|
||||||
// Internal dashboard: always revalidate so UI fixes reach the browser immediately.
|
|
||||||
OnPrepareResponse = ctx => ctx.Context.Response.Headers.CacheControl = "no-cache"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register both Read and Control endpoints for the local host
|
|
||||||
app.MapPredictalyticsReadEndpoints();
|
|
||||||
app.MapPredictalyticsControlEndpoints();
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers all Read-only endpoints designed for viewing and analysis.
|
/// Registers all Read-only endpoints designed for viewing and analysis.
|
||||||
/// Safely exposed publicly since these endpoints perform no DB writes.
|
/// Safely exposed publicly since these endpoints perform no DB writes.
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public static class DevEndpoints
|
|||||||
// its raw SQL referenced non-existent columns/tables (Trades.Type/Payout,
|
// its raw SQL referenced non-existent columns/tables (Trades.Type/Payout,
|
||||||
// Traders.LastPositionsUpdatedAt, table "Jobs") and would have deleted ALL
|
// Traders.LastPositionsUpdatedAt, table "Jobs") and would have deleted ALL
|
||||||
// TraderPositions including the pruned-history conserves. The supported
|
// TraderPositions including the pruned-history conserves. The supported
|
||||||
// repair path is the "Recalculate All Traders" action in the WinForms
|
// repair path is the "Recalculate All Traders" action in the Avalonia
|
||||||
// Development menu (EmbeddedWebServer.RunRecalculateAllTradersAsync).
|
// Development menu (EmbeddedWebServer.RunRecalculateAllTradersAsync).
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,10 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RootNamespace>Predictalytics.Api</RootNamespace>
|
<RootNamespace>Predictalytics.Api</RootNamespace>
|
||||||
<!-- This project is used as a library by WinFormsHost; it does not run standalone -->
|
<!-- Bibliothek fuer Predictalytics.Hosting, laeuft nicht eigenstaendig. -->
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Predictalytics.Application\Predictalytics.Application.csproj" />
|
<ProjectReference Include="..\Predictalytics.Application\Predictalytics.Application.csproj" />
|
||||||
<ProjectReference Include="..\Predictalytics.Infrastructure\Predictalytics.Infrastructure.csproj" />
|
<ProjectReference Include="..\Predictalytics.Infrastructure\Predictalytics.Infrastructure.csproj" />
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ public interface IAnalyticsService
|
|||||||
Task<IReadOnlyList<Services.ShowcaseSection>> GetShowcasesAsync(CancellationToken ct = default);
|
Task<IReadOnlyList<Services.ShowcaseSection>> GetShowcasesAsync(CancellationToken ct = default);
|
||||||
|
|
||||||
/// <summary>Get list of all discovered traits.</summary>
|
/// <summary>Get list of all discovered traits.</summary>
|
||||||
Task<IReadOnlyList<string>> GetTraitsAsync(CancellationToken ct = default);
|
|
||||||
|
|
||||||
/// <summary>Get a trader's details.</summary>
|
/// <summary>Get a trader's details.</summary>
|
||||||
Task<TraderDetailDto?> GetTraderDetailAsync(int traderId, CancellationToken ct = default);
|
Task<TraderDetailDto?> GetTraderDetailAsync(int traderId, CancellationToken ct = default);
|
||||||
|
|||||||
@@ -242,13 +242,6 @@ public class AnalyticsService : IAnalyticsService
|
|||||||
return ShowcaseBuilder.Build(candidates);
|
return ShowcaseBuilder.Build(candidates);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IReadOnlyList<string>> GetTraitsAsync(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
// Actually this is unused since we mapped it straight to db context in the endpoint,
|
|
||||||
// but required by interface.
|
|
||||||
return Task.FromResult<IReadOnlyList<string>>(new List<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IReadOnlyList<MarketDto>> GetMarketsAsync(int skip = 0, int take = 50, string? platform = null, string? category = null, string? query = null, CancellationToken ct = default)
|
public async Task<IReadOnlyList<MarketDto>> GetMarketsAsync(int skip = 0, int take = 50, string? platform = null, string? category = null, string? query = null, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// NOTE: Currently IMarketRepository.GetActiveAsync doesn't support pagination/filtering.
|
// NOTE: Currently IMarketRepository.GetActiveAsync doesn't support pagination/filtering.
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ namespace Predictalytics.Hosting;
|
|||||||
/// Plattformneutraler Kern der Anwendung: Lebenszyklus des eingebetteten
|
/// Plattformneutraler Kern der Anwendung: Lebenszyklus des eingebetteten
|
||||||
/// Kestrel-Webservers, der Hintergrund-Worker und der manuellen Wartungsaktionen.
|
/// Kestrel-Webservers, der Hintergrund-Worker und der manuellen Wartungsaktionen.
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Enthaelt bewusst keinerlei UI-Bezug — die Hosts (WinForms heute, Avalonia kuenftig,
|
/// Enthaelt bewusst keinerlei UI-Bezug — die Avalonia-Shell und der
|
||||||
/// sowie der Headless-Modus) setzen darauf auf.
|
/// Headless-Modus setzen gleichermassen darauf auf.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PredictalyticsHost
|
public sealed class PredictalyticsHost
|
||||||
|
|||||||
@@ -14,9 +14,6 @@
|
|||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
|
||||||
<PackageReference Include="Serilog" />
|
<PackageReference Include="Serilog" />
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.Console" />
|
|
||||||
<PackageReference Include="Serilog.Formatting.Compact" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.2 KiB |
@@ -1,173 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="./support.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<x-dc>
|
|
||||||
<helmet>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Roboto+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
body { margin: 0; background: #0a0d14; font-family: 'Manrope', system-ui, sans-serif; }
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
a { color: #5b9dff; text-decoration: none; }
|
|
||||||
a:hover { color: #8ab8ff; }
|
|
||||||
::-webkit-scrollbar { width: 8px; }
|
|
||||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 4px; }
|
|
||||||
pre { margin: 0; }
|
|
||||||
</style>
|
|
||||||
</helmet>
|
|
||||||
|
|
||||||
<div style="position:relative; min-height:100vh; width:100%; background: radial-gradient(1100px 700px at 15% -10%, rgba(22,82,240,0.22), transparent 60%), #0a0d14; color:#EDEFF5;">
|
|
||||||
|
|
||||||
<header style="position:sticky; top:0; z-index:50; display:flex; align-items:center; justify-content:space-between; padding:16px 48px; backdrop-filter:blur(20px); background:rgba(10,13,20,0.6); border-bottom:1px solid rgba(255,255,255,0.07);">
|
|
||||||
<div style="display:flex; align-items:center; gap:10px;">
|
|
||||||
<div style="width:30px; height:30px; border-radius:9px; background:linear-gradient(135deg,#1652F0,#4c8dff); box-shadow:0 0 20px rgba(22,82,240,0.6);"></div>
|
|
||||||
<div style="font-weight:800; font-size:17px; letter-spacing:-0.02em;">{{ brandName }}<span style="color:#5b9dff;">.</span></div>
|
|
||||||
</div>
|
|
||||||
<nav style="display:flex; align-items:center; gap:32px;">
|
|
||||||
<a href="./Landing.dc.html#features" style="font-size:13.5px; font-weight:600; color:#C7CCDA;">Features</a>
|
|
||||||
<a href="./Landing.dc.html#pricing" style="font-size:13.5px; font-weight:600; color:#C7CCDA;">Pricing</a>
|
|
||||||
<a href="./Landing.dc.html#faq" style="font-size:13.5px; font-weight:600; color:#C7CCDA;">FAQ</a>
|
|
||||||
<div style="font-size:13.5px; font-weight:700; color:#5b9dff;">API Docs</div>
|
|
||||||
</nav>
|
|
||||||
<a href="./Landing.dc.html" style="padding:9px 20px; border-radius:10px; font-size:13.5px; font-weight:700; background:linear-gradient(135deg,#1652F0,#4c8dff);">Get API key</a>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div style="display:flex; max-width:1280px; margin:0 auto;">
|
|
||||||
<!-- SIDEBAR -->
|
|
||||||
<aside style="width:230px; flex:none; padding:36px 16px; position:sticky; top:68px; align-self:flex-start; height:calc(100vh - 68px); overflow-y:auto;">
|
|
||||||
<div style="font-size:11px; font-weight:700; color:#5B6377; letter-spacing:0.06em; padding:0 12px 8px;">GETTING STARTED</div>
|
|
||||||
<sc-for list="{{ sidebarTop }}" as="s" hint-placeholder-count="3">
|
|
||||||
<div onClick="{{ s.onClick }}" style="cursor:pointer; padding:9px 12px; border-radius:9px; font-size:13.5px; font-weight:600; color:{{ s.color }}; background:{{ s.bg }}; margin-bottom:2px;">{{ s.label }}</div>
|
|
||||||
</sc-for>
|
|
||||||
<div style="font-size:11px; font-weight:700; color:#5B6377; letter-spacing:0.06em; padding:16px 12px 8px;">ENDPOINTS</div>
|
|
||||||
<sc-for list="{{ sidebarEndpoints }}" as="s" hint-placeholder-count="6">
|
|
||||||
<div onClick="{{ s.onClick }}" style="cursor:pointer; display:flex; align-items:center; gap:8px; padding:9px 12px; border-radius:9px; font-size:13px; font-weight:600; color:{{ s.color }}; background:{{ s.bg }}; margin-bottom:2px;">
|
|
||||||
<span style="font-size:10px; font-weight:800; font-family:'Roboto Mono'; color:{{ s.methodColor }};">{{ s.method }}</span>
|
|
||||||
<span>{{ s.label }}</span>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- CONTENT -->
|
|
||||||
<main style="flex:1; min-width:0; padding:36px 40px 100px;">
|
|
||||||
<h1 style="margin:0 0 10px; font-size:30px; font-weight:800; letter-spacing:-0.02em;">API Reference</h1>
|
|
||||||
<p style="margin:0 0 32px; color:#8B93A7; font-size:14.5px; max-width:640px; line-height:1.6;">Programmatic access to every trader and market metric in {{ brandName }}. All endpoints return JSON over HTTPS.</p>
|
|
||||||
|
|
||||||
<!-- Auth -->
|
|
||||||
<section style="margin-bottom:40px;">
|
|
||||||
<h2 style="font-size:19px; font-weight:800; margin:0 0 12px;">Authentication</h2>
|
|
||||||
<p style="color:#8B93A7; font-size:13.5px; line-height:1.7; margin:0 0 14px;">Pass your API key as a bearer token on every request. Keys are available from your account dashboard after signing up.</p>
|
|
||||||
<div style="border-radius:14px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); padding:18px 20px; font-family:'Roboto Mono'; font-size:13px; color:#C7CCDA; overflow-x:auto;">
|
|
||||||
<pre>curl https://api.predictalytics.io/v1/markets \
|
|
||||||
-H "Authorization: Bearer YOUR_API_KEY"</pre>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Rate limits -->
|
|
||||||
<section style="margin-bottom:40px;">
|
|
||||||
<h2 style="font-size:19px; font-weight:800; margin:0 0 12px;">Rate limits</h2>
|
|
||||||
<div style="display:grid; grid-template-columns:repeat(3,1fr); gap:14px;">
|
|
||||||
<sc-for list="{{ rateLimits }}" as="r" hint-placeholder-count="3">
|
|
||||||
<div style="border-radius:14px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); padding:16px 18px;">
|
|
||||||
<div style="font-size:12.5px; font-weight:700; color:#8B93A7; margin-bottom:6px;">{{ r.plan }}</div>
|
|
||||||
<div style="font-size:20px; font-weight:800; font-family:'Roboto Mono';">{{ r.limit }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Endpoints -->
|
|
||||||
<sc-for list="{{ endpoints }}" as="ep" hint-placeholder-count="6">
|
|
||||||
<section id="{{ ep.id }}" style="margin-bottom:40px; scroll-margin-top:80px;">
|
|
||||||
<div style="display:flex; align-items:center; gap:12px; margin-bottom:10px;">
|
|
||||||
<span style="font-size:11px; font-weight:800; font-family:'Roboto Mono'; padding:4px 9px; border-radius:6px; background:{{ ep.methodBg }}; color:{{ ep.methodColor }};">{{ ep.method }}</span>
|
|
||||||
<span style="font-family:'Roboto Mono'; font-size:14.5px; font-weight:700; color:#EDEFF5;">{{ ep.path }}</span>
|
|
||||||
</div>
|
|
||||||
<p style="color:#8B93A7; font-size:13.5px; line-height:1.6; margin:0 0 14px;">{{ ep.desc }}</p>
|
|
||||||
|
|
||||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:14px;">
|
|
||||||
<div style="border-radius:14px; background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.07); padding:16px 18px;">
|
|
||||||
<div style="font-size:11px; font-weight:700; color:#5B6377; text-transform:uppercase; margin-bottom:10px;">Parameters</div>
|
|
||||||
<sc-for list="{{ ep.params }}" as="p" hint-placeholder-count="2">
|
|
||||||
<div style="display:flex; align-items:baseline; gap:8px; margin-bottom:8px; font-size:12.5px;">
|
|
||||||
<span style="font-family:'Roboto Mono'; font-weight:700; color:#5b9dff;">{{ p.name }}</span>
|
|
||||||
<span style="color:#5B6377; font-size:11.5px;">{{ p.type }}</span>
|
|
||||||
<span style="color:#8B93A7;">{{ p.desc }}</span>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
<div style="border-radius:14px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); padding:16px 18px; font-family:'Roboto Mono'; font-size:12px; color:#C7CCDA; overflow-x:auto;">
|
|
||||||
<pre>{{ ep.example }}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</sc-for>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</x-dc>
|
|
||||||
<script type="text/x-dc" data-dc-script data-props="{"$preview": {"width": 1440}, "brandName": {"editor": "text", "default": "Predictalytics", "tsType": "string"}}">
|
|
||||||
class Component extends DCLogic {
|
|
||||||
scrollTo(id) {
|
|
||||||
const el = document.getElementById(id);
|
|
||||||
if (el) el.scrollIntoView({ block: "start" });
|
|
||||||
}
|
|
||||||
|
|
||||||
renderVals() {
|
|
||||||
const brandName = this.props.brandName ?? "Predictalytics";
|
|
||||||
|
|
||||||
const endpoints = [
|
|
||||||
{ id: "get-markets", method: "GET", path: "/v1/markets", methodBg: "rgba(18,212,138,0.14)", methodColor: "#12D48A",
|
|
||||||
desc: "List all markets with current price, volume, and liquidity.",
|
|
||||||
params: [{ name: "category", type: "string", desc: "Filter by category" }, { name: "status", type: "string", desc: "active | resolved" }],
|
|
||||||
example: `{\n "id": "fed-sep-cut",\n "question": "Fed cuts rates...",\n "yesPrice": 71.4,\n "volume24h": 2840000\n}` },
|
|
||||||
{ id: "get-market", method: "GET", path: "/v1/markets/{id}", methodBg: "rgba(18,212,138,0.14)", methodColor: "#12D48A",
|
|
||||||
desc: "Get detailed data for a single market including order book and holders.",
|
|
||||||
params: [{ name: "id", type: "string", desc: "Market identifier" }],
|
|
||||||
example: `{\n "orderBook": { "bids": [...], "asks": [...] },\n "holders": [ { "trader": "0xA1...", "size": 420000 } ]\n}` },
|
|
||||||
{ id: "get-traders", method: "GET", path: "/v1/traders", methodBg: "rgba(18,212,138,0.14)", methodColor: "#12D48A",
|
|
||||||
desc: "Search and list traders with P&L, win rate, and traits.",
|
|
||||||
params: [{ name: "trait", type: "string", desc: "Bot | Arbitrage Trader | Resolution Farming" }, { name: "sort", type: "string", desc: "pnl | volume" }],
|
|
||||||
example: `{\n "handle": "quant_owl",\n "traits": ["Arbitrage Trader"],\n "winRate": 68.4\n}` },
|
|
||||||
{ id: "get-trader", method: "GET", path: "/v1/traders/{id}", methodBg: "rgba(18,212,138,0.14)", methodColor: "#12D48A",
|
|
||||||
desc: "Get a trader's full position history and connected wallets.",
|
|
||||||
params: [{ name: "id", type: "string", desc: "Trader handle or wallet" }],
|
|
||||||
example: `{\n "positions": [...],\n "connectedWallets": ["0x88Ac...2f01"]\n}` },
|
|
||||||
{ id: "post-alerts", method: "POST", path: "/v1/alerts", methodBg: "rgba(22,82,240,0.16)", methodColor: "#5b9dff",
|
|
||||||
desc: "Create an alert on a trader or market.",
|
|
||||||
params: [{ name: "target", type: "string", desc: "Trader or market id" }, { name: "condition", type: "string", desc: "price_move | new_position" }],
|
|
||||||
example: `{\n "id": "alert_881",\n "target": "arb_meridian",\n "condition": "new_position"\n}` },
|
|
||||||
{ id: "get-agent", method: "GET", path: "/v1/mcp/context", methodBg: "rgba(18,212,138,0.14)", methodColor: "#12D48A",
|
|
||||||
desc: "Enterprise only. MCP-compatible context feed for your own AI agent.",
|
|
||||||
params: [{ name: "scope", type: "string", desc: "markets | traders | both" }],
|
|
||||||
example: `{\n "mcp_version": "1.0",\n "resources": ["markets", "traders"]\n}` },
|
|
||||||
];
|
|
||||||
|
|
||||||
const sidebarTop = [
|
|
||||||
{ label: "Authentication", color: "#C7CCDA", bg: "transparent", onClick: () => this.scrollTo("auth") },
|
|
||||||
{ label: "Rate limits", color: "#C7CCDA", bg: "transparent", onClick: () => this.scrollTo("rate-limits") },
|
|
||||||
];
|
|
||||||
const sidebarEndpoints = endpoints.map((ep) => ({
|
|
||||||
label: ep.path, method: ep.method, methodColor: ep.methodColor, color: "#C7CCDA", bg: "transparent",
|
|
||||||
onClick: () => this.scrollTo(ep.id),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const rateLimits = [
|
|
||||||
{ plan: "Free", limit: "20 / day" },
|
|
||||||
{ plan: "Basic", limit: "200 / day" },
|
|
||||||
{ plan: "Enterprise", limit: "2,000 / day" },
|
|
||||||
];
|
|
||||||
|
|
||||||
return { brandName, endpoints, sidebarTop, sidebarEndpoints, rateLimits };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="./support.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<x-dc>
|
|
||||||
<helmet>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Roboto+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
body { margin: 0; background: #0a0d14; font-family: 'Manrope', system-ui, sans-serif; }
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
a { color: #5b9dff; text-decoration: none; }
|
|
||||||
a:hover { color: #8ab8ff; }
|
|
||||||
::-webkit-scrollbar { width: 8px; }
|
|
||||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 4px; }
|
|
||||||
</style>
|
|
||||||
</helmet>
|
|
||||||
|
|
||||||
<div style="position:relative; min-height:100vh; width:100%; background: radial-gradient(1100px 700px at 15% -10%, rgba(22,82,240,0.28), transparent 60%), radial-gradient(900px 600px at 110% 10%, rgba(18,212,138,0.10), transparent 55%), #0a0d14; color:#EDEFF5; overflow-x:hidden;">
|
|
||||||
|
|
||||||
<!-- NAV -->
|
|
||||||
<header style="position:sticky; top:0; z-index:50; display:flex; align-items:center; justify-content:space-between; padding:16px 48px; backdrop-filter:blur(20px); background:rgba(10,13,20,0.6); border-bottom:1px solid rgba(255,255,255,0.07);">
|
|
||||||
<div style="display:flex; align-items:center; gap:10px;">
|
|
||||||
<div style="width:30px; height:30px; border-radius:9px; background:linear-gradient(135deg,#1652F0,#4c8dff); box-shadow:0 0 20px rgba(22,82,240,0.6);"></div>
|
|
||||||
<div style="font-weight:800; font-size:17px; letter-spacing:-0.02em;">{{ brandName }}<span style="color:#5b9dff;">.</span></div>
|
|
||||||
</div>
|
|
||||||
<nav style="display:flex; align-items:center; gap:32px;">
|
|
||||||
<a href="#features" style="font-size:13.5px; font-weight:600; color:#C7CCDA;">Features</a>
|
|
||||||
<a href="#pricing" style="font-size:13.5px; font-weight:600; color:#C7CCDA;">Pricing</a>
|
|
||||||
<a href="#faq" style="font-size:13.5px; font-weight:600; color:#C7CCDA;">FAQ</a>
|
|
||||||
<a href="./API Docs.dc.html" style="font-size:13.5px; font-weight:600; color:#C7CCDA;">API Docs</a>
|
|
||||||
</nav>
|
|
||||||
<div style="display:flex; align-items:center; gap:10px;">
|
|
||||||
<div onClick="{{ openLogin }}" style="cursor:pointer; padding:9px 18px; border-radius:10px; font-size:13.5px; font-weight:700; color:#EDEFF5;">Log in</div>
|
|
||||||
<div onClick="{{ openRegister }}" style="cursor:pointer; padding:9px 20px; border-radius:10px; font-size:13.5px; font-weight:700; background:linear-gradient(135deg,#1652F0,#4c8dff); box-shadow:0 4px 20px rgba(22,82,240,0.4);">Sign up</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- HERO -->
|
|
||||||
<section style="padding:100px 48px 80px; max-width:1080px; margin:0 auto; text-align:center; display:flex; flex-direction:column; align-items:center; gap:22px;">
|
|
||||||
<div style="padding:6px 14px; border-radius:20px; background:rgba(22,82,240,0.12); border:1px solid rgba(22,82,240,0.3); font-size:12.5px; font-weight:700; color:#5b9dff;">Live on-chain analytics · Polymarket & beyond</div>
|
|
||||||
<h1 style="margin:0; font-size:52px; font-weight:800; letter-spacing:-0.03em; line-height:1.08; max-width:760px;">See every trader and market before the crowd does</h1>
|
|
||||||
<p style="margin:0; font-size:17px; color:#8B93A7; max-width:600px; line-height:1.6;">Deep trader profiling, wallet clustering, and real-time market analytics for professional prediction market traders.</p>
|
|
||||||
<div style="display:flex; gap:14px; margin-top:8px;">
|
|
||||||
<div onClick="{{ openRegister }}" style="cursor:pointer; padding:14px 26px; border-radius:12px; font-size:14.5px; font-weight:700; background:linear-gradient(135deg,#1652F0,#4c8dff); box-shadow:0 8px 30px rgba(22,82,240,0.4);">Start free</div>
|
|
||||||
<a href="#features" style="padding:14px 26px; border-radius:12px; font-size:14.5px; font-weight:700; color:#EDEFF5; border:1px solid rgba(255,255,255,0.14); background:rgba(255,255,255,0.04); white-space:nowrap;">See features</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- product preview mock -->
|
|
||||||
<div style="margin-top:40px; width:100%; border-radius:20px; background:rgba(255,255,255,0.045); border:1px solid rgba(255,255,255,0.09); backdrop-filter:blur(24px); box-shadow:0 20px 60px rgba(0,0,0,0.4); padding:20px; display:grid; grid-template-columns:1.4fr 1fr; gap:14px; text-align:left;">
|
|
||||||
<div style="border-radius:14px; background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.07); padding:18px;">
|
|
||||||
<div style="font-size:12px; font-weight:700; color:#8B93A7; margin-bottom:10px;">Yes Price History</div>
|
|
||||||
<svg viewBox="0 0 500 140" style="width:100%; height:140px;">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="hfill" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0%" stop-color="#12D48A" stop-opacity="0.35"/>
|
|
||||||
<stop offset="100%" stop-color="#12D48A" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path d="{{ heroArea }}" fill="url(#hfill)" stroke="none"></path>
|
|
||||||
<path d="{{ heroLine }}" fill="none" stroke="#12D48A" stroke-width="2.5"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div style="border-radius:14px; background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.07); padding:18px; display:flex; flex-direction:column; gap:10px;">
|
|
||||||
<div style="font-size:12px; font-weight:700; color:#8B93A7;">Top Traders</div>
|
|
||||||
<sc-for list="{{ heroTraders }}" as="t" hint-placeholder-count="3">
|
|
||||||
<div style="display:flex; align-items:center; gap:10px;">
|
|
||||||
<div style="width:24px; height:24px; border-radius:7px; background:{{ t.bg }};"></div>
|
|
||||||
<div style="font-size:12.5px; font-weight:700; flex:1;">{{ t.handle }}</div>
|
|
||||||
<div style="font-size:12px; font-family:'Roboto Mono'; font-weight:700; color:#12D48A;">{{ t.pnl }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- FEATURES -->
|
|
||||||
<section id="features" style="padding:80px 48px; max-width:1200px; margin:0 auto;">
|
|
||||||
<div style="text-align:center; margin-bottom:48px;">
|
|
||||||
<div style="font-size:12.5px; font-weight:700; color:#5b9dff; margin-bottom:10px;">FEATURES</div>
|
|
||||||
<h2 style="margin:0 0 12px; font-size:34px; font-weight:800; letter-spacing:-0.02em;">Everything you need to read the market</h2>
|
|
||||||
<p style="margin:0; color:#8B93A7; font-size:15px;">Built for quants who need signal, not noise.</p>
|
|
||||||
</div>
|
|
||||||
<div style="display:grid; grid-template-columns:repeat(3,1fr); gap:18px;">
|
|
||||||
<sc-for list="{{ features }}" as="f" hint-placeholder-count="6">
|
|
||||||
<div style="padding:26px; border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px);">
|
|
||||||
<div style="width:42px; height:42px; border-radius:12px; background:{{ f.iconBg }}; margin-bottom:16px; display:flex; align-items:center; justify-content:center;">
|
|
||||||
<div style="width:16px; height:16px; border-radius:5px; background:{{ f.dotColor }};"></div>
|
|
||||||
</div>
|
|
||||||
<div style="font-size:16px; font-weight:700; margin-bottom:8px;">{{ f.title }}</div>
|
|
||||||
<div style="font-size:13.5px; color:#8B93A7; line-height:1.6;">{{ f.desc }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- PRICING -->
|
|
||||||
<section id="pricing" style="padding:80px 48px; max-width:1160px; margin:0 auto;">
|
|
||||||
<div style="text-align:center; margin-bottom:48px;">
|
|
||||||
<div style="font-size:12.5px; font-weight:700; color:#5b9dff; margin-bottom:10px;">PRICING</div>
|
|
||||||
<h2 style="margin:0 0 12px; font-size:34px; font-weight:800; letter-spacing:-0.02em;">Simple, usage-based plans</h2>
|
|
||||||
<p style="margin:0; color:#8B93A7; font-size:15px;">Upgrade any time as your call volume grows.</p>
|
|
||||||
</div>
|
|
||||||
<div style="display:grid; grid-template-columns:repeat(3,1fr); gap:20px; align-items:stretch;">
|
|
||||||
<sc-for list="{{ plans }}" as="p" hint-placeholder-count="3">
|
|
||||||
<div style="{{ p.cardStyle }}">
|
|
||||||
<sc-if value="{{ p.featured }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<div style="position:absolute; top:-13px; left:50%; transform:translateX(-50%); padding:5px 14px; border-radius:20px; background:linear-gradient(135deg,#1652F0,#4c8dff); font-size:11px; font-weight:800; letter-spacing:0.04em;">MOST POPULAR</div>
|
|
||||||
</sc-if>
|
|
||||||
<div style="font-size:15px; font-weight:700; margin-bottom:6px;">{{ p.name }}</div>
|
|
||||||
<div style="display:flex; align-items:baseline; gap:4px; margin-bottom:18px;">
|
|
||||||
<div style="font-size:36px; font-weight:800; font-family:'Roboto Mono'; letter-spacing:-0.02em;">{{ p.price }}</div>
|
|
||||||
<div style="font-size:13px; color:#8B93A7;">{{ p.period }}</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; flex-direction:column; gap:10px; margin-bottom:22px;">
|
|
||||||
<sc-for list="{{ p.items }}" as="item" hint-placeholder-count="4">
|
|
||||||
<div style="display:flex; align-items:center; gap:9px; font-size:13.5px; color:#C7CCDA;">
|
|
||||||
<div style="width:6px; height:6px; border-radius:50%; background:#12D48A; flex:none;"></div>
|
|
||||||
<span>{{ item }}</span>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
<div onClick="{{ openRegister }}" style="{{ p.btnStyle }}">{{ p.btnLabel }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- FAQ -->
|
|
||||||
<section id="faq" style="padding:80px 48px 100px; max-width:820px; margin:0 auto;">
|
|
||||||
<div style="text-align:center; margin-bottom:44px;">
|
|
||||||
<div style="font-size:12.5px; font-weight:700; color:#5b9dff; margin-bottom:10px;">FAQ</div>
|
|
||||||
<h2 style="margin:0; font-size:34px; font-weight:800; letter-spacing:-0.02em;">Common questions</h2>
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; flex-direction:column; gap:10px;">
|
|
||||||
<sc-for list="{{ faqs }}" as="q" hint-placeholder-count="6">
|
|
||||||
<div style="border-radius:14px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); overflow:hidden;">
|
|
||||||
<div onClick="{{ q.onToggle }}" style="cursor:pointer; padding:18px 22px; display:flex; align-items:center; justify-content:space-between; gap:16px;">
|
|
||||||
<div style="font-size:14.5px; font-weight:700;">{{ q.question }}</div>
|
|
||||||
<div style="flex:none; width:20px; height:20px; display:flex; align-items:center; justify-content:center; font-size:16px; color:#5b9dff; transform:{{ q.iconTransform }};">+</div>
|
|
||||||
</div>
|
|
||||||
<sc-if value="{{ q.open }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<div style="padding:0 22px 18px; font-size:13.5px; color:#8B93A7; line-height:1.7;">{{ q.answer }}</div>
|
|
||||||
</sc-if>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- FOOTER -->
|
|
||||||
<footer style="padding:32px 48px; border-top:1px solid rgba(255,255,255,0.07); display:flex; align-items:center; justify-content:space-between; color:#5B6377; font-size:12.5px;">
|
|
||||||
<div>© 2026 {{ brandName }}. All rights reserved.</div>
|
|
||||||
<div style="display:flex; gap:20px;">
|
|
||||||
<a href="./API Docs.dc.html" style="color:#5B6377;">API Docs</a>
|
|
||||||
<a href="#faq" style="color:#5B6377;">FAQ</a>
|
|
||||||
<a href="#pricing" style="color:#5B6377;">Pricing</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<!-- AUTH MODAL -->
|
|
||||||
<sc-if value="{{ authOpen }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<div onClick="{{ closeAuth }}" style="position:fixed; inset:0; z-index:100; background:rgba(6,8,13,0.7); backdrop-filter:blur(6px); display:flex; align-items:center; justify-content:center;">
|
|
||||||
<div onClick="{{ stopProp }}" style="width:400px; max-width:92vw; border-radius:20px; background:rgba(16,20,29,0.9); border:1px solid rgba(255,255,255,0.1); backdrop-filter:blur(30px); box-shadow:0 30px 80px rgba(0,0,0,0.5); padding:32px;">
|
|
||||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:24px;">
|
|
||||||
<div style="font-size:19px; font-weight:800;">{{ authTitle }}</div>
|
|
||||||
<div onClick="{{ closeAuth }}" style="cursor:pointer; width:28px; height:28px; border-radius:8px; background:rgba(255,255,255,0.06); display:flex; align-items:center; justify-content:center; font-size:14px; color:#8B93A7;">✕</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; flex-direction:column; gap:14px;">
|
|
||||||
<sc-if value="{{ isRegisterMode }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<div style="display:flex; flex-direction:column; gap:6px;">
|
|
||||||
<label style="font-size:12.5px; font-weight:600; color:#C7CCDA;">Full name</label>
|
|
||||||
<input placeholder="Jane Trader" style="padding:11px 14px; border-radius:10px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:13.5px; font-family:'Manrope'; outline:none;" />
|
|
||||||
</div>
|
|
||||||
</sc-if>
|
|
||||||
<div style="display:flex; flex-direction:column; gap:6px;">
|
|
||||||
<label style="font-size:12.5px; font-weight:600; color:#C7CCDA;">Email</label>
|
|
||||||
<input placeholder="you@domain.com" style="padding:11px 14px; border-radius:10px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:13.5px; font-family:'Manrope'; outline:none;" />
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; flex-direction:column; gap:6px;">
|
|
||||||
<label style="font-size:12.5px; font-weight:600; color:#C7CCDA;">Password</label>
|
|
||||||
<input type="password" placeholder="••••••••" style="padding:11px 14px; border-radius:10px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:13.5px; font-family:'Manrope'; outline:none;" />
|
|
||||||
</div>
|
|
||||||
<div style="margin-top:6px; padding:13px; border-radius:11px; text-align:center; font-size:14px; font-weight:700; cursor:pointer; background:linear-gradient(135deg,#1652F0,#4c8dff); box-shadow:0 8px 24px rgba(22,82,240,0.4);">{{ authSubmitLabel }}</div>
|
|
||||||
<div style="text-align:center; font-size:12.5px; color:#8B93A7; margin-top:4px;">
|
|
||||||
{{ authSwitchPrompt }} <span onClick="{{ switchAuthMode }}" style="color:#5b9dff; font-weight:700; cursor:pointer;">{{ authSwitchAction }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sc-if>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</x-dc>
|
|
||||||
<script type="text/x-dc" data-dc-script data-props="{"$preview": {"width": 1440}, "brandName": {"editor": "text", "default": "Predictalytics", "tsType": "string"}}">
|
|
||||||
class Component extends DCLogic {
|
|
||||||
state = {
|
|
||||||
authOpen: false,
|
|
||||||
authMode: "login",
|
|
||||||
openFaq: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
openLogin = () => this.setState({ authOpen: true, authMode: "login" });
|
|
||||||
openRegister = () => this.setState({ authOpen: true, authMode: "register" });
|
|
||||||
closeAuth = () => this.setState({ authOpen: false });
|
|
||||||
switchAuthMode = () => this.setState((s) => ({ authMode: s.authMode === "login" ? "register" : "login" }));
|
|
||||||
stopProp = (e) => e.stopPropagation();
|
|
||||||
toggleFaq = (i) => this.setState((s) => ({ openFaq: s.openFaq === i ? -1 : i }));
|
|
||||||
|
|
||||||
buildPath(series, w, h, pad) {
|
|
||||||
const min = Math.min(...series);
|
|
||||||
const max = Math.max(...series);
|
|
||||||
const range = max - min || 1;
|
|
||||||
const innerW = w - pad * 2;
|
|
||||||
const innerH = h - pad * 2;
|
|
||||||
const pts = series.map((v, i) => {
|
|
||||||
const x = pad + (i / (series.length - 1)) * innerW;
|
|
||||||
const y = pad + innerH - ((v - min) / range) * innerH;
|
|
||||||
return [x, y];
|
|
||||||
});
|
|
||||||
let line = "M" + pts[0][0].toFixed(1) + "," + pts[0][1].toFixed(1);
|
|
||||||
for (let i = 1; i < pts.length; i++) line += " L" + pts[i][0].toFixed(1) + "," + pts[i][1].toFixed(1);
|
|
||||||
const area = line + ` L${pts[pts.length - 1][0].toFixed(1)},${h - pad} L${pts[0][0].toFixed(1)},${h - pad} Z`;
|
|
||||||
return { line, area };
|
|
||||||
}
|
|
||||||
|
|
||||||
renderVals() {
|
|
||||||
const brandName = this.props.brandName ?? "Predictalytics";
|
|
||||||
const heroSeries = [40, 44, 41, 48, 52, 50, 58, 55, 62, 66, 63, 70, 68, 74, 71.4];
|
|
||||||
const { line, area } = this.buildPath(heroSeries, 500, 140, 8);
|
|
||||||
|
|
||||||
const features = [
|
|
||||||
{ title: "Trader Tracking", desc: "Follow any wallet's full position history, P&L, and behavior patterns across every market.", iconBg: "rgba(22,82,240,0.14)", dotColor: "#5b9dff" },
|
|
||||||
{ title: "Wallet Clustering", desc: "Detect connected wallets and coordinated accounts behind a single trading strategy.", iconBg: "rgba(124,92,255,0.14)", dotColor: "#7c5cff" },
|
|
||||||
{ title: "Market Analysis", desc: "Order book depth, liquidity, and holder concentration for every active market.", iconBg: "rgba(18,212,138,0.14)", dotColor: "#12D48A" },
|
|
||||||
{ title: "Custom Alerts", desc: "Get notified the moment a tracked trader opens a position or a market shifts sharply.", iconBg: "rgba(255,157,76,0.14)", dotColor: "#ff9d4c" },
|
|
||||||
{ title: "Trader Traits", desc: "Automatic tagging — arbitrage traders, resolution farmers, bots — at a glance.", iconBg: "rgba(246,70,93,0.14)", dotColor: "#F6465D" },
|
|
||||||
{ title: "API Access", desc: "Pull every metric programmatically into your own models and dashboards.", iconBg: "rgba(22,82,240,0.14)", dotColor: "#5b9dff" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const baseCard = "position:relative; padding:30px 26px; border-radius:20px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); display:flex; flex-direction:column;";
|
|
||||||
const featuredCard = "position:relative; padding:30px 26px; border-radius:20px; background:rgba(22,82,240,0.08); border:1px solid rgba(22,82,240,0.35); backdrop-filter:blur(24px); display:flex; flex-direction:column; box-shadow:0 20px 50px rgba(22,82,240,0.2);";
|
|
||||||
const btnPrimary = "cursor:pointer; padding:12px; border-radius:11px; text-align:center; font-size:13.5px; font-weight:700; margin-top:auto; background:linear-gradient(135deg,#1652F0,#4c8dff); box-shadow:0 8px 24px rgba(22,82,240,0.4);";
|
|
||||||
const btnSecondary = "cursor:pointer; padding:12px; border-radius:11px; text-align:center; font-size:13.5px; font-weight:700; margin-top:auto; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.12); color:#EDEFF5;";
|
|
||||||
|
|
||||||
const plans = [
|
|
||||||
{ name: "Free", price: "$0", period: "/ month", featured: false, cardStyle: baseCard, btnStyle: btnSecondary, btnLabel: "Get started",
|
|
||||||
items: ["20 API calls / day", "Restricted web access", "Market overview only", "Community support"] },
|
|
||||||
{ name: "Pro", price: "$99", period: "/ month", featured: true, cardStyle: featuredCard, btnStyle: btnPrimary, btnLabel: "Start Pro",
|
|
||||||
items: ["2,000 API calls / day", "Enhanced AI trader analytics", "MCP access for your AI agent", "Priority support"] },
|
|
||||||
{ name: "Enterprise", price: "Contact us", period: "", featured: false, cardStyle: baseCard, btnStyle: btnSecondary, btnLabel: "Contact sales",
|
|
||||||
items: ["10,000+ API calls / day", "Ready to use AI analytics with your individual prompts", "MCP access for your AI agent", "Priority support"] },
|
|
||||||
];
|
|
||||||
|
|
||||||
const faqDefs = [
|
|
||||||
{ q: "What data sources power your analytics?", a: "We index on-chain activity directly from Polymarket and related prediction market contracts in real time, plus supplementary off-chain resolution data." },
|
|
||||||
{ q: "How do you identify traders as bots or arbitrageurs?", a: "We apply behavioral heuristics — trade timing, position sizing, cross-market correlation — to automatically tag wallets with traits like Bot, Arbitrage Trader, or Resolution Farming." },
|
|
||||||
{ q: "Can I track a specific wallet or trader?", a: "Yes. Search any wallet address or handle to pull up its full position history, P&L, and connected wallets." },
|
|
||||||
{ q: "What's included in the API?", a: "Market and trader endpoints covering prices, order books, holders, and trader metrics. Call limits scale with your plan, from 20 to 2,000 requests per day." },
|
|
||||||
{ q: "Do you support alerts?", a: "Yes, set alerts on tracked traders or markets and get notified the moment a position opens or a price moves sharply." },
|
|
||||||
{ q: "Can I cancel or change my plan anytime?", a: "Yes, upgrade, downgrade, or cancel at any time from your account settings — changes apply at the next billing cycle." },
|
|
||||||
];
|
|
||||||
const faqs = faqDefs.map((f, i) => ({
|
|
||||||
question: f.q,
|
|
||||||
answer: f.a,
|
|
||||||
open: this.state.openFaq === i,
|
|
||||||
iconTransform: this.state.openFaq === i ? "rotate(45deg)" : "rotate(0deg)",
|
|
||||||
onToggle: () => this.toggleFaq(i),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const heroTraders = [
|
|
||||||
{ handle: "quant_owl", pnl: "+$216K", bg: "linear-gradient(135deg,#1652F0,#4c8dff)" },
|
|
||||||
{ handle: "arb_meridian", pnl: "+$454K", bg: "linear-gradient(135deg,#12D48A,#0a8f5f)" },
|
|
||||||
{ handle: "resolvr", pnl: "+$105K", bg: "linear-gradient(135deg,#7c5cff,#4c8dff)" },
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
brandName,
|
|
||||||
heroLine: line,
|
|
||||||
heroArea: area,
|
|
||||||
heroTraders,
|
|
||||||
features,
|
|
||||||
plans,
|
|
||||||
faqs,
|
|
||||||
authOpen: this.state.authOpen,
|
|
||||||
isRegisterMode: this.state.authMode === "register",
|
|
||||||
authTitle: this.state.authMode === "login" ? "Log in" : "Create your account",
|
|
||||||
authSubmitLabel: this.state.authMode === "login" ? "Log in" : "Create account",
|
|
||||||
authSwitchPrompt: this.state.authMode === "login" ? "Don't have an account?" : "Already have an account?",
|
|
||||||
authSwitchAction: this.state.authMode === "login" ? "Sign up" : "Log in",
|
|
||||||
openLogin: this.openLogin,
|
|
||||||
openRegister: this.openRegister,
|
|
||||||
closeAuth: this.closeAuth,
|
|
||||||
switchAuthMode: this.switchAuthMode,
|
|
||||||
stopProp: this.stopProp,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,875 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="./support.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<x-dc>
|
|
||||||
<helmet>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Roboto+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
body { margin: 0; background: #0a0d14; font-family: 'Manrope', system-ui, sans-serif; overflow-x: auto; }
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
||||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 4px; }
|
|
||||||
::-webkit-scrollbar-track { background: transparent; }
|
|
||||||
a { color: #5b9dff; text-decoration: none; }
|
|
||||||
a:hover { color: #8ab8ff; }
|
|
||||||
table { border-collapse: collapse; }
|
|
||||||
.dual-range { -webkit-appearance: none; appearance: none; background: transparent; pointer-events: none; margin: 0; position: absolute; top: 0; left: 0; width: 100%; height: 20px; }
|
|
||||||
.dual-range::-webkit-slider-thumb { -webkit-appearance: none; pointer-events: auto; width: 15px; height: 15px; border-radius: 50%; background: #5b9dff; border: 2px solid #0a0d14; cursor: pointer; box-shadow: 0 0 6px rgba(91,157,255,0.7); margin-top: 0; }
|
|
||||||
.dual-range::-moz-range-thumb { pointer-events: auto; width: 15px; height: 15px; border-radius: 50%; background: #5b9dff; border: 2px solid #0a0d14; cursor: pointer; box-shadow: 0 0 6px rgba(91,157,255,0.7); }
|
|
||||||
.dual-range::-webkit-slider-runnable-track { background: transparent; }
|
|
||||||
.dual-range::-moz-range-track { background: transparent; }
|
|
||||||
</style>
|
|
||||||
</helmet>
|
|
||||||
|
|
||||||
<div style="position:relative; min-height:100vh; min-width:1440px; width:100%; background: radial-gradient(1100px 700px at 15% -10%, rgba(22,82,240,0.28), transparent 60%), radial-gradient(900px 600px at 110% 10%, rgba(18,212,138,0.10), transparent 55%), #0a0d14; color:#EDEFF5;">
|
|
||||||
|
|
||||||
<!-- decorative glow layer -->
|
|
||||||
<div style="position:fixed; inset:0; pointer-events:none; background: radial-gradient(600px 400px at 80% 85%, rgba(76,141,255,0.10), transparent 60%); z-index:0;"></div>
|
|
||||||
|
|
||||||
<div style="position:relative; z-index:1; display:flex; min-height:100vh;">
|
|
||||||
|
|
||||||
<!-- SIDEBAR -->
|
|
||||||
<aside style="width:236px; flex:none; padding:24px 16px; display:flex; flex-direction:column; gap:28px; border-right:1px solid rgba(255,255,255,0.07); background:rgba(255,255,255,0.015);">
|
|
||||||
<div style="display:flex; align-items:center; gap:10px; padding:4px 8px;">
|
|
||||||
<div style="width:30px; height:30px; border-radius:9px; background:linear-gradient(135deg,#1652F0,#4c8dff); box-shadow:0 0 20px rgba(22,82,240,0.6);"></div>
|
|
||||||
<div style="font-weight:800; font-size:17px; letter-spacing:-0.02em;">Lucid<span style="color:#5b9dff;">.</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav style="display:flex; flex-direction:column; gap:2px;">
|
|
||||||
<div style="font:600 10px/1 'Manrope'; letter-spacing:0.08em; color:#5B6377; padding:0 12px 8px;">NAVIGATION</div>
|
|
||||||
<sc-for list="{{ navItems }}" as="item" hint-placeholder-count="4">
|
|
||||||
<div onClick="{{ item.onClick }}" style="{{ item.style }}">
|
|
||||||
<div style="{{ item.dotStyle }}"></div>
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div style="margin-top:auto; display:flex; flex-direction:column; gap:10px;">
|
|
||||||
<div style="padding:14px; border-radius:14px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(20px);">
|
|
||||||
<div style="font-size:11px; color:#8B93A7; margin-bottom:6px;">Data feed</div>
|
|
||||||
<div style="display:flex; align-items:center; gap:7px;">
|
|
||||||
<div style="width:7px; height:7px; border-radius:50%; background:#12D48A; box-shadow:0 0 8px #12D48A;"></div>
|
|
||||||
<div style="font-size:12.5px; font-weight:600;">Live · on-chain</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; align-items:center; gap:10px; padding:8px 10px;">
|
|
||||||
<div style="width:30px; height:30px; border-radius:50%; background:linear-gradient(135deg,#2a2f3d,#1a1e28); border:1px solid rgba(255,255,255,0.1);"></div>
|
|
||||||
<div>
|
|
||||||
<div style="font-size:12.5px; font-weight:700;">Analyst</div>
|
|
||||||
<div style="font-size:11px; color:#5B6377;">Pro workspace</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- MAIN -->
|
|
||||||
<main style="flex:1; min-width:0; padding:22px 32px 60px;">
|
|
||||||
|
|
||||||
<!-- TOPBAR -->
|
|
||||||
<div style="display:flex; align-items:center; gap:16px; margin-bottom:22px;">
|
|
||||||
<div style="flex:1; position:relative; max-width:420px;">
|
|
||||||
<input placeholder="Search markets, traders, wallets…" style="width:100%; padding:11px 14px 11px 38px; border-radius:11px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.09); color:#EDEFF5; font-size:13px; font-family:'Manrope'; outline:none;" />
|
|
||||||
<div style="position:absolute; left:13px; top:50%; transform:translateY(-50%); width:14px; height:14px; border-radius:50%; border:2px solid #5B6377;"></div>
|
|
||||||
</div>
|
|
||||||
<div style="margin-left:auto; display:flex; align-items:center; gap:10px;">
|
|
||||||
<div style="padding:8px 14px; border-radius:10px; background:rgba(18,212,138,0.10); border:1px solid rgba(18,212,138,0.25); font-size:12px; font-weight:700; color:#12D48A; font-family:'Roboto Mono';">24H VOL {{ headerVolume }}</div>
|
|
||||||
<div style="width:38px; height:38px; border-radius:11px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.09); display:flex; align-items:center; justify-content:center;">
|
|
||||||
<div style="width:8px; height:8px; border-radius:50%; background:#F6465D;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- breadcrumb -->
|
|
||||||
<div style="display:flex; align-items:center; gap:8px; font-size:12.5px; margin-bottom:18px; font-weight:600;">
|
|
||||||
<sc-for list="{{ breadcrumbItems }}" as="crumb" hint-placeholder-count="1">
|
|
||||||
<span onClick="{{ crumb.onClick }}" style="{{ crumb.style }}">{{ crumb.label }}</span>
|
|
||||||
<sc-if value="{{ crumb.sep }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<span style="color:#3a4152;">›</span>
|
|
||||||
</sc-if>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<sc-if value="{{ isOverview }}" hint-placeholder-val="{{ true }}">
|
|
||||||
<!-- ================= OVERVIEW ================= -->
|
|
||||||
<div>
|
|
||||||
<div style="display:flex; align-items:baseline; justify-content:space-between; margin-bottom:20px;">
|
|
||||||
<div>
|
|
||||||
<h1 style="margin:0 0 4px; font-size:26px; font-weight:800; letter-spacing:-0.02em;">Markets Overview</h1>
|
|
||||||
<div style="font-size:13px; color:#8B93A7;">Real-time analytics across all active prediction markets</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- stat cards -->
|
|
||||||
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:20px;">
|
|
||||||
<sc-for list="{{ globalStats }}" as="stat" hint-placeholder-count="4">
|
|
||||||
<div style="padding:18px 20px; border-radius:16px; background:rgba(255,255,255,0.045); border:1px solid rgba(255,255,255,0.09); backdrop-filter:blur(24px); box-shadow:0 8px 30px rgba(0,0,0,0.25);">
|
|
||||||
<div style="font-size:12px; color:#8B93A7; font-weight:600; margin-bottom:10px;">{{ stat.label }}</div>
|
|
||||||
<div style="font-size:24px; font-weight:800; font-family:'Roboto Mono'; letter-spacing:-0.02em;">{{ stat.value }}</div>
|
|
||||||
<div style="font-size:12px; font-weight:700; margin-top:6px; font-family:'Roboto Mono'; color:{{ stat.deltaColor }};">{{ stat.delta }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:grid; grid-template-columns:1.7fr 1fr; gap:16px; align-items:start;">
|
|
||||||
<!-- markets table -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
|
|
||||||
<div style="padding:18px 22px 14px; font-size:15px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">All Markets</div>
|
|
||||||
<table style="width:100%; font-size:13px;">
|
|
||||||
<thead>
|
|
||||||
<tr style="color:#5B6377; font-size:11px; text-transform:uppercase; letter-spacing:0.04em;">
|
|
||||||
<td style="padding:10px 22px;">Market</td>
|
|
||||||
<td style="padding:10px 10px; text-align:right;">Yes %</td>
|
|
||||||
<td style="padding:10px 10px; text-align:right;">24h</td>
|
|
||||||
<td style="padding:10px 10px; text-align:right;">Volume</td>
|
|
||||||
<td style="padding:10px 22px; text-align:right;">Traders</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<sc-for list="{{ marketRows }}" as="m" hint-placeholder-count="6">
|
|
||||||
<tr onClick="{{ m.onClick }}" style="cursor:pointer; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
|
|
||||||
<td style="padding:13px 22px;">
|
|
||||||
<div style="font-weight:700; font-size:13.5px; margin-bottom:3px;">{{ m.question }}</div>
|
|
||||||
<div style="font-size:11.5px; color:#5B6377;">{{ m.category }} · {{ m.statusLabel }}</div>
|
|
||||||
</td>
|
|
||||||
<td style="padding:13px 10px; text-align:right; font-family:'Roboto Mono'; font-weight:700;">{{ m.yesPriceLabel }}</td>
|
|
||||||
<td style="padding:13px 10px; text-align:right; font-family:'Roboto Mono'; font-weight:700; color:{{ m.changeColor }};">{{ m.changeLabel }}</td>
|
|
||||||
<td style="padding:13px 10px; text-align:right; font-family:'Roboto Mono'; color:#C7CCDA;">{{ m.volumeLabel }}</td>
|
|
||||||
<td style="padding:13px 22px; text-align:right; font-family:'Roboto Mono'; color:#8B93A7;">{{ m.tradersLabel }}</td>
|
|
||||||
</tr>
|
|
||||||
</sc-for>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- leaderboard -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
|
|
||||||
<div style="padding:18px 22px 14px; font-size:15px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">Top Traders</div>
|
|
||||||
<div style="display:flex; flex-direction:column;">
|
|
||||||
<sc-for list="{{ leaderboard }}" as="t" hint-placeholder-count="6">
|
|
||||||
<div onClick="{{ t.onClick }}" style="cursor:pointer; display:flex; align-items:center; gap:12px; padding:12px 22px; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
|
|
||||||
<div style="width:30px; height:30px; border-radius:9px; flex:none; background:{{ t.avatarBg }}; display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:800;">{{ t.initial }}</div>
|
|
||||||
<div style="min-width:0; flex:1;">
|
|
||||||
<div style="font-weight:700; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">{{ t.handle }}</div>
|
|
||||||
<div style="font-size:11px; color:#5B6377;">{{ t.trait }}</div>
|
|
||||||
</div>
|
|
||||||
<div style="text-align:right; flex:none;">
|
|
||||||
<div style="font-family:'Roboto Mono'; font-weight:700; font-size:13px; color:#12D48A;">{{ t.pnlLabel }}</div>
|
|
||||||
<div style="font-size:10.5px; color:#5B6377;">P&L</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sc-if>
|
|
||||||
|
|
||||||
<sc-if value="{{ isMarket }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<!-- ================= MARKET DETAIL ================= -->
|
|
||||||
<div>
|
|
||||||
<div style="display:flex; align-items:flex-start; justify-content:space-between; margin-bottom:20px; gap:20px;">
|
|
||||||
<div>
|
|
||||||
<div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;">
|
|
||||||
<div style="font-size:11px; font-weight:700; padding:4px 10px; border-radius:7px; background:rgba(22,82,240,0.14); border:1px solid rgba(22,82,240,0.3); color:#5b9dff;">{{ marketDetail.category }}</div>
|
|
||||||
<div style="font-size:11px; font-weight:700; padding:4px 10px; border-radius:7px; background:{{ marketDetail.statusBg }}; color:{{ marketDetail.statusColor }};">{{ marketDetail.status }}</div>
|
|
||||||
</div>
|
|
||||||
<h1 style="margin:0 0 6px; font-size:24px; font-weight:800; letter-spacing:-0.02em; max-width:640px;">{{ marketDetail.question }}</h1>
|
|
||||||
<div style="font-size:12.5px; color:#8B93A7;">Resolves {{ marketDetail.resolves }}</div>
|
|
||||||
</div>
|
|
||||||
<div style="text-align:right; flex:none;">
|
|
||||||
<div style="font-size:34px; font-weight:800; font-family:'Roboto Mono'; color:#12D48A;">{{ marketDetail.yesPriceBig }}</div>
|
|
||||||
<div style="font-size:12px; color:#8B93A7; font-weight:600;">YES probability</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:18px;">
|
|
||||||
<sc-for list="{{ marketStats }}" as="stat" hint-placeholder-count="4">
|
|
||||||
<div style="padding:16px 18px; border-radius:14px; background:rgba(255,255,255,0.045); border:1px solid rgba(255,255,255,0.09); backdrop-filter:blur(24px);">
|
|
||||||
<div style="font-size:11.5px; color:#8B93A7; font-weight:600; margin-bottom:8px;">{{ stat.label }}</div>
|
|
||||||
<div style="font-size:19px; font-weight:800; font-family:'Roboto Mono';">{{ stat.value }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:grid; grid-template-columns:1.6fr 1fr; gap:16px; margin-bottom:16px; align-items:start;">
|
|
||||||
<!-- price chart -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:20px 22px;">
|
|
||||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:14px;">
|
|
||||||
<div style="font-size:14px; font-weight:700;">Yes Price History</div>
|
|
||||||
<div style="display:flex; gap:6px;">
|
|
||||||
<div style="font-size:11px; padding:5px 10px; border-radius:7px; background:rgba(22,82,240,0.18); color:#5b9dff; font-weight:700;">30D</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<svg viewBox="0 0 600 220" style="width:100%; height:220px; overflow:visible;">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="fillGrad" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0%" stop-color="#12D48A" stop-opacity="0.35"/>
|
|
||||||
<stop offset="100%" stop-color="#12D48A" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="glow"><feGaussianBlur stdDeviation="3.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
|
|
||||||
</defs>
|
|
||||||
<path d="{{ marketDetail.areaPath }}" fill="url(#fillGrad)" stroke="none"></path>
|
|
||||||
<path d="{{ marketDetail.linePath }}" fill="none" stroke="#12D48A" stroke-width="2.5" filter="url(#glow)"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- order book -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:18px 20px;">
|
|
||||||
<div style="font-size:14px; font-weight:700; margin-bottom:12px;">Order Book Depth</div>
|
|
||||||
<div style="font-size:10.5px; color:#5B6377; font-weight:700; text-transform:uppercase; margin-bottom:6px;">Asks</div>
|
|
||||||
<sc-for list="{{ marketDetail.asks }}" as="row" hint-placeholder-count="5">
|
|
||||||
<div style="position:relative; margin-bottom:3px; height:20px;">
|
|
||||||
<div style="position:absolute; right:0; top:0; bottom:0; width:{{ row.barWidth }}; background:rgba(246,70,93,0.16); border-radius:4px;"></div>
|
|
||||||
<div style="position:relative; display:flex; justify-content:space-between; padding:0 8px; font-size:11.5px; font-family:'Roboto Mono'; line-height:20px;">
|
|
||||||
<span style="color:#F6465D; font-weight:700;">{{ row.priceLabel }}</span>
|
|
||||||
<span style="color:#8B93A7;">{{ row.sizeLabel }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
<div style="height:1px; background:rgba(255,255,255,0.08); margin:8px 0;"></div>
|
|
||||||
<div style="font-size:10.5px; color:#5B6377; font-weight:700; text-transform:uppercase; margin-bottom:6px;">Bids</div>
|
|
||||||
<sc-for list="{{ marketDetail.bids }}" as="row" hint-placeholder-count="5">
|
|
||||||
<div style="position:relative; margin-bottom:3px; height:20px;">
|
|
||||||
<div style="position:absolute; right:0; top:0; bottom:0; width:{{ row.barWidth }}; background:rgba(18,212,138,0.16); border-radius:4px;"></div>
|
|
||||||
<div style="position:relative; display:flex; justify-content:space-between; padding:0 8px; font-size:11.5px; font-family:'Roboto Mono'; line-height:20px;">
|
|
||||||
<span style="color:#12D48A; font-weight:700;">{{ row.priceLabel }}</span>
|
|
||||||
<span style="color:#8B93A7;">{{ row.sizeLabel }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:16px;">
|
|
||||||
<!-- biggest holders -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
|
|
||||||
<div style="padding:16px 20px 12px; font-size:14px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">Biggest Holders</div>
|
|
||||||
<table style="width:100%; font-size:12.5px;">
|
|
||||||
<tbody>
|
|
||||||
<sc-for list="{{ marketDetail.holders }}" as="h" hint-placeholder-count="5">
|
|
||||||
<tr onClick="{{ h.onClick }}" style="cursor:pointer; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
|
|
||||||
<td style="padding:11px 20px; font-family:'Roboto Mono'; font-weight:700;">{{ h.trader }}</td>
|
|
||||||
<td style="padding:11px 6px; text-align:center;"><span style="padding:3px 9px; border-radius:6px; font-size:11px; font-weight:700; background:{{ h.sideBg }}; color:{{ h.sideColor }};">{{ h.side }}</span></td>
|
|
||||||
<td style="padding:11px 20px; text-align:right; font-family:'Roboto Mono'; color:#C7CCDA;">{{ h.valueLabel }}</td>
|
|
||||||
</tr>
|
|
||||||
</sc-for>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- top traders in market -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
|
|
||||||
<div style="padding:16px 20px 12px; font-size:14px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">Top Traders Here</div>
|
|
||||||
<div style="display:flex; flex-direction:column;">
|
|
||||||
<sc-for list="{{ marketDetail.topTraders }}" as="t" hint-placeholder-count="4">
|
|
||||||
<div onClick="{{ t.onClick }}" style="cursor:pointer; display:flex; align-items:center; gap:12px; padding:12px 20px; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
|
|
||||||
<div style="width:28px; height:28px; border-radius:8px; flex:none; background:{{ t.avatarBg }}; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:800;">{{ t.initial }}</div>
|
|
||||||
<div style="font-weight:700; font-size:13px; flex:1;">{{ t.handle }}</div>
|
|
||||||
<div style="font-size:11px; color:#8B93A7;">{{ t.trait }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sc-if>
|
|
||||||
|
|
||||||
<sc-if value="{{ isTraderSearch }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<!-- ================= TRADER SEARCH ================= -->
|
|
||||||
<div>
|
|
||||||
<div style="margin-bottom:20px;">
|
|
||||||
<h1 style="margin:0 0 4px; font-size:26px; font-weight:800; letter-spacing:-0.02em;">Trader Search</h1>
|
|
||||||
<div style="font-size:13px; color:#8B93A7;">Filter by traits and metric ranges to find the traders you care about</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:grid; grid-template-columns:300px 1fr; gap:16px; align-items:start;">
|
|
||||||
<!-- filter panel -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:20px; display:flex; flex-direction:column; gap:22px; position:sticky; top:22px;">
|
|
||||||
<div>
|
|
||||||
<input value="{{ searchText }}" onChange="{{ onSearchTextChange }}" placeholder="Handle or wallet…" style="width:100%; padding:10px 13px; border-radius:10px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:13px; font-family:'Manrope'; outline:none;" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div style="font-size:12px; font-weight:700; color:#C7CCDA; margin-bottom:10px;">Traits</div>
|
|
||||||
<div style="display:flex; flex-wrap:wrap; gap:7px;">
|
|
||||||
<sc-for list="{{ traitFilterChips }}" as="chip" hint-placeholder-count="5">
|
|
||||||
<div onClick="{{ chip.onClick }}" style="{{ chip.style }}">{{ chip.label }}</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div style="display:flex; justify-content:space-between; margin-bottom:10px;">
|
|
||||||
<span style="font-size:12px; font-weight:700; color:#C7CCDA;">Win Rate</span>
|
|
||||||
<span style="font-size:11px; font-family:'Roboto Mono'; color:#8B93A7;">{{ winRateSlider.minLabel }} – {{ winRateSlider.maxLabel }}</span>
|
|
||||||
</div>
|
|
||||||
<div style="position:relative; height:20px; margin-bottom:10px;">
|
|
||||||
<div style="position:absolute; top:8px; left:0; right:0; height:4px; border-radius:2px; background:rgba(255,255,255,0.1);"></div>
|
|
||||||
<div style="position:absolute; top:8px; height:4px; border-radius:2px; background:#5b9dff; left:{{ winRateSlider.fillLeft }}; right:{{ winRateSlider.fillRight }};"></div>
|
|
||||||
<input class="dual-range" type="range" min="{{ winRateSlider.lo }}" max="{{ winRateSlider.hi }}" value="{{ winRateSlider.minVal }}" onInput="{{ winRateSlider.onMinInput }}" />
|
|
||||||
<input class="dual-range" type="range" min="{{ winRateSlider.lo }}" max="{{ winRateSlider.hi }}" value="{{ winRateSlider.maxVal }}" onInput="{{ winRateSlider.onMaxInput }}" />
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; align-items:center; gap:8px;">
|
|
||||||
<input value="{{ winRateSlider.minVal }}" onBlur="{{ winRateSlider.onMinTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
|
|
||||||
<span style="color:#5B6377; font-size:11px;">–</span>
|
|
||||||
<input value="{{ winRateSlider.maxVal }}" onBlur="{{ winRateSlider.onMaxTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div style="display:flex; justify-content:space-between; margin-bottom:10px;">
|
|
||||||
<span style="font-size:12px; font-weight:700; color:#C7CCDA;">Volume</span>
|
|
||||||
<span style="font-size:11px; font-family:'Roboto Mono'; color:#8B93A7;">{{ volumeSlider.minLabel }} – {{ volumeSlider.maxLabel }}</span>
|
|
||||||
</div>
|
|
||||||
<div style="position:relative; height:20px; margin-bottom:10px;">
|
|
||||||
<div style="position:absolute; top:8px; left:0; right:0; height:4px; border-radius:2px; background:rgba(255,255,255,0.1);"></div>
|
|
||||||
<div style="position:absolute; top:8px; height:4px; border-radius:2px; background:#5b9dff; left:{{ volumeSlider.fillLeft }}; right:{{ volumeSlider.fillRight }};"></div>
|
|
||||||
<input class="dual-range" type="range" min="{{ volumeSlider.lo }}" max="{{ volumeSlider.hi }}" step="500000" value="{{ volumeSlider.minVal }}" onInput="{{ volumeSlider.onMinInput }}" />
|
|
||||||
<input class="dual-range" type="range" min="{{ volumeSlider.lo }}" max="{{ volumeSlider.hi }}" step="500000" value="{{ volumeSlider.maxVal }}" onInput="{{ volumeSlider.onMaxInput }}" />
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; align-items:center; gap:8px;">
|
|
||||||
<input value="{{ volumeSlider.minVal }}" onBlur="{{ volumeSlider.onMinTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
|
|
||||||
<span style="color:#5B6377; font-size:11px;">–</span>
|
|
||||||
<input value="{{ volumeSlider.maxVal }}" onBlur="{{ volumeSlider.onMaxTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div style="display:flex; justify-content:space-between; margin-bottom:10px;">
|
|
||||||
<span style="font-size:12px; font-weight:700; color:#C7CCDA;">Total P&L</span>
|
|
||||||
<span style="font-size:11px; font-family:'Roboto Mono'; color:#8B93A7;">{{ pnlSlider.minLabel }} – {{ pnlSlider.maxLabel }}</span>
|
|
||||||
</div>
|
|
||||||
<div style="position:relative; height:20px; margin-bottom:10px;">
|
|
||||||
<div style="position:absolute; top:8px; left:0; right:0; height:4px; border-radius:2px; background:rgba(255,255,255,0.1);"></div>
|
|
||||||
<div style="position:absolute; top:8px; height:4px; border-radius:2px; background:#5b9dff; left:{{ pnlSlider.fillLeft }}; right:{{ pnlSlider.fillRight }};"></div>
|
|
||||||
<input class="dual-range" type="range" min="{{ pnlSlider.lo }}" max="{{ pnlSlider.hi }}" step="10000" value="{{ pnlSlider.minVal }}" onInput="{{ pnlSlider.onMinInput }}" />
|
|
||||||
<input class="dual-range" type="range" min="{{ pnlSlider.lo }}" max="{{ pnlSlider.hi }}" step="10000" value="{{ pnlSlider.maxVal }}" onInput="{{ pnlSlider.onMaxInput }}" />
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; align-items:center; gap:8px;">
|
|
||||||
<input value="{{ pnlSlider.minVal }}" onBlur="{{ pnlSlider.onMinTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
|
|
||||||
<span style="color:#5B6377; font-size:11px;">–</span>
|
|
||||||
<input value="{{ pnlSlider.maxVal }}" onBlur="{{ pnlSlider.onMaxTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div onClick="{{ onResetFilters }}" style="cursor:pointer; text-align:center; padding:10px; border-radius:10px; font-size:12.5px; font-weight:700; color:#8B93A7; border:1px solid rgba(255,255,255,0.1);">Reset filters</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- results -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
|
|
||||||
<div style="padding:16px 22px; font-size:14px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06); display:flex; justify-content:space-between;">
|
|
||||||
<span>Results</span><span style="color:#5B6377; font-weight:600;">{{ searchResultsCount }} traders</span>
|
|
||||||
</div>
|
|
||||||
<table style="width:100%; font-size:13px;">
|
|
||||||
<thead>
|
|
||||||
<tr style="color:#5B6377; font-size:11px; text-transform:uppercase; letter-spacing:0.04em;">
|
|
||||||
<td style="padding:10px 22px;">Trader</td>
|
|
||||||
<td style="padding:10px 10px;">Traits</td>
|
|
||||||
<td style="padding:10px 10px; text-align:right;">Win Rate</td>
|
|
||||||
<td style="padding:10px 10px; text-align:right;">Volume</td>
|
|
||||||
<td style="padding:10px 22px; text-align:right;">P&L</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<sc-for list="{{ searchResults }}" as="t" hint-placeholder-count="6">
|
|
||||||
<tr onClick="{{ t.onClick }}" style="cursor:pointer; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
|
|
||||||
<td style="padding:12px 22px;">
|
|
||||||
<div style="display:flex; align-items:center; gap:10px;">
|
|
||||||
<div style="width:26px; height:26px; border-radius:8px; flex:none; background:{{ t.avatarBg }}; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:800;">{{ t.initial }}</div>
|
|
||||||
<span style="font-weight:700;">{{ t.handle }}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td style="padding:12px 10px;">
|
|
||||||
<div style="display:flex; gap:5px; flex-wrap:wrap;">
|
|
||||||
<sc-for list="{{ t.traitChips }}" as="chip" hint-placeholder-count="2">
|
|
||||||
<div style="font-size:10.5px; font-weight:700; padding:3px 8px; border-radius:6px; background:{{ chip.bg }}; color:{{ chip.color }};">{{ chip.label }}</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono'; font-weight:700;">{{ t.winRateLabel }}</td>
|
|
||||||
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono'; color:#C7CCDA;">{{ t.volumeLabel }}</td>
|
|
||||||
<td style="padding:12px 22px; text-align:right; font-family:'Roboto Mono'; font-weight:700; color:{{ t.pnlColor }};">{{ t.pnlLabel }}</td>
|
|
||||||
</tr>
|
|
||||||
</sc-for>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sc-if>
|
|
||||||
|
|
||||||
<sc-if value="{{ isTrader }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<!-- ================= TRADER DETAIL ================= -->
|
|
||||||
<div>
|
|
||||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:20px; gap:20px;">
|
|
||||||
<div style="display:flex; align-items:center; gap:16px;">
|
|
||||||
<div style="width:56px; height:56px; border-radius:16px; background:{{ traderDetail.avatarBg }}; display:flex; align-items:center; justify-content:center; font-size:20px; font-weight:800; box-shadow:0 0 24px rgba(22,82,240,0.3);">{{ traderDetail.initial }}</div>
|
|
||||||
<div>
|
|
||||||
<div style="display:flex; align-items:center; gap:10px; margin-bottom:4px;">
|
|
||||||
<h1 style="margin:0; font-size:22px; font-weight:800; letter-spacing:-0.02em;">{{ traderDetail.handle }}</h1>
|
|
||||||
<div style="font-size:12px; font-family:'Roboto Mono'; color:#5B6377;">{{ traderDetail.wallet }}</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; gap:6px;">
|
|
||||||
<sc-for list="{{ traderDetail.traitChips }}" as="chip" hint-placeholder-count="2">
|
|
||||||
<div style="font-size:11px; font-weight:700; padding:4px 10px; border-radius:7px; background:{{ chip.bg }}; color:{{ chip.color }}; border:1px solid {{ chip.border }};">{{ chip.label }}</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="text-align:right;">
|
|
||||||
<div style="font-size:11.5px; color:#8B93A7; margin-bottom:2px;">Member since {{ traderDetail.joined }}</div>
|
|
||||||
<div style="font-size:11.5px; color:#8B93A7;">{{ traderDetail.activePositionsLabel }} active positions</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:grid; grid-template-columns:repeat(5,1fr); gap:16px; margin-bottom:18px;">
|
|
||||||
<sc-for list="{{ traderStats }}" as="stat" hint-placeholder-count="5">
|
|
||||||
<div style="padding:16px 18px; border-radius:14px; background:rgba(255,255,255,0.045); border:1px solid rgba(255,255,255,0.09); backdrop-filter:blur(24px);">
|
|
||||||
<div style="font-size:11px; color:#8B93A7; font-weight:600; margin-bottom:8px;">{{ stat.label }}</div>
|
|
||||||
<div style="font-size:18px; font-weight:800; font-family:'Roboto Mono'; color:{{ stat.color }};">{{ stat.value }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:grid; grid-template-columns:1.6fr 1fr; gap:16px; margin-bottom:16px; align-items:start;">
|
|
||||||
<!-- pnl chart -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:20px 22px;">
|
|
||||||
<div style="font-size:14px; font-weight:700; margin-bottom:14px;">P&L Over Time</div>
|
|
||||||
<svg viewBox="0 0 600 200" style="width:100%; height:200px; overflow:visible;">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="pnlFill" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0%" stop-color="#5b9dff" stop-opacity="0.35"/>
|
|
||||||
<stop offset="100%" stop-color="#5b9dff" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="glow2"><feGaussianBlur stdDeviation="3.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
|
|
||||||
</defs>
|
|
||||||
<path d="{{ traderDetail.areaPath }}" fill="url(#pnlFill)" stroke="none"></path>
|
|
||||||
<path d="{{ traderDetail.linePath }}" fill="none" stroke="#5b9dff" stroke-width="2.5" filter="url(#glow2)"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- network -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:18px 20px;">
|
|
||||||
<div style="font-size:14px; font-weight:700; margin-bottom:10px;">Connected Wallets</div>
|
|
||||||
<sc-if value="{{ traderDetail.hasConnections }}" hint-placeholder-val="{{ true }}">
|
|
||||||
<sc-for list="{{ traderDetail.connections }}" as="w" hint-placeholder-count="2">
|
|
||||||
<div style="display:flex; align-items:center; gap:10px; padding:10px 0; border-top:1px solid rgba(255,255,255,0.05);">
|
|
||||||
<div style="width:8px; height:8px; border-radius:50%; background:#F6465D; box-shadow:0 0 6px #F6465D;"></div>
|
|
||||||
<div style="font-family:'Roboto Mono'; font-size:12.5px; color:#C7CCDA;">{{ w }}</div>
|
|
||||||
</div>
|
|
||||||
</sc-for>
|
|
||||||
</sc-if>
|
|
||||||
<sc-if value="{{ traderDetail.noConnections }}" hint-placeholder-val="{{ false }}">
|
|
||||||
<div style="font-size:12.5px; color:#5B6377; padding:10px 0;">No linked wallets detected.</div>
|
|
||||||
</sc-if>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- positions table -->
|
|
||||||
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
|
|
||||||
<div style="padding:16px 22px 12px; font-size:14px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">Positions</div>
|
|
||||||
<table style="width:100%; font-size:12.5px;">
|
|
||||||
<thead>
|
|
||||||
<tr style="color:#5B6377; font-size:11px; text-transform:uppercase; letter-spacing:0.04em;">
|
|
||||||
<td style="padding:10px 22px;">Market</td>
|
|
||||||
<td style="padding:10px 10px; text-align:center;">Side</td>
|
|
||||||
<td style="padding:10px 10px; text-align:right;">Size</td>
|
|
||||||
<td style="padding:10px 10px; text-align:right;">Entry</td>
|
|
||||||
<td style="padding:10px 10px; text-align:right;">Current</td>
|
|
||||||
<td style="padding:10px 22px; text-align:right;">P&L</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<sc-for list="{{ traderDetail.positions }}" as="p" hint-placeholder-count="4">
|
|
||||||
<tr style="border-top:1px solid rgba(255,255,255,0.05);">
|
|
||||||
<td style="padding:12px 22px; font-weight:600;">{{ p.market }}</td>
|
|
||||||
<td style="padding:12px 10px; text-align:center;"><span style="padding:3px 9px; border-radius:6px; font-size:11px; font-weight:700; background:{{ p.sideBg }}; color:{{ p.sideColor }};">{{ p.side }}</span></td>
|
|
||||||
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono';">{{ p.sizeLabel }}</td>
|
|
||||||
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono'; color:#8B93A7;">{{ p.entryLabel }}</td>
|
|
||||||
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono'; color:#8B93A7;">{{ p.currentLabel }}</td>
|
|
||||||
<td style="padding:12px 22px; text-align:right; font-family:'Roboto Mono'; font-weight:700; color:{{ p.pnlColor }};">{{ p.pnlLabel }}</td>
|
|
||||||
</tr>
|
|
||||||
</sc-for>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sc-if>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</x-dc>
|
|
||||||
<script type="text/x-dc" data-dc-script>
|
|
||||||
class Component extends DCLogic {
|
|
||||||
state = {
|
|
||||||
view: "overview",
|
|
||||||
selectedMarketId: null,
|
|
||||||
selectedTraderId: null,
|
|
||||||
markets: null,
|
|
||||||
traders: null,
|
|
||||||
searchText: "",
|
|
||||||
filterTraits: [],
|
|
||||||
winRateMin: 0,
|
|
||||||
winRateMax: 100,
|
|
||||||
volumeMin: 0,
|
|
||||||
volumeMax: 25000000,
|
|
||||||
pnlMin: -50000,
|
|
||||||
pnlMax: 500000,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
import("./market-data.js").then((m) => {
|
|
||||||
this.setState({ markets: m.MARKETS, traders: m.TRADERS });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
goOverview = () => this.setState({ view: "overview" });
|
|
||||||
goMarket = (id) => this.setState({ view: "market", selectedMarketId: id });
|
|
||||||
goTrader = (id) => this.setState({ view: "trader", selectedTraderId: id });
|
|
||||||
goTraderSearch = () => this.setState({ view: "traderSearch" });
|
|
||||||
|
|
||||||
setSearchText = (e) => this.setState({ searchText: e.target.value });
|
|
||||||
toggleTrait = (trait) => this.setState((s) => ({
|
|
||||||
filterTraits: s.filterTraits.includes(trait) ? s.filterTraits.filter((t) => t !== trait) : [...s.filterTraits, trait],
|
|
||||||
}));
|
|
||||||
setRange = (key) => (e) => this.setState({ [key]: Number(e.target.value) });
|
|
||||||
resetFilters = () => this.setState({
|
|
||||||
searchText: "", filterTraits: [], winRateMin: 0, winRateMax: 100,
|
|
||||||
volumeMin: 0, volumeMax: 25000000, pnlMin: -50000, pnlMax: 500000,
|
|
||||||
});
|
|
||||||
|
|
||||||
parseClamp(raw, lo, hi) {
|
|
||||||
const n = Number(String(raw).replace(/[^0-9.\-]/g, ""));
|
|
||||||
if (Number.isNaN(n)) return null;
|
|
||||||
return Math.max(lo, Math.min(hi, n));
|
|
||||||
}
|
|
||||||
onMinTextBlur = (key, lo, hi) => (e) => {
|
|
||||||
const n = this.parseClamp(e.target.value, lo, hi);
|
|
||||||
this.setState({ [key]: n === null ? lo : n });
|
|
||||||
};
|
|
||||||
onMaxTextBlur = (key, lo, hi) => (e) => {
|
|
||||||
const n = this.parseClamp(e.target.value, lo, hi);
|
|
||||||
this.setState({ [key]: n === null ? hi : n });
|
|
||||||
};
|
|
||||||
|
|
||||||
fmtMoney(n) {
|
|
||||||
if (n >= 1000000) return "$" + (n / 1000000).toFixed(2) + "M";
|
|
||||||
if (n >= 1000) return "$" + (n / 1000).toFixed(1) + "K";
|
|
||||||
return "$" + Math.round(n);
|
|
||||||
}
|
|
||||||
fmtNum(n) {
|
|
||||||
if (n >= 1000) return (n / 1000).toFixed(1) + "K";
|
|
||||||
return String(Math.round(n));
|
|
||||||
}
|
|
||||||
fmtSigned(n, suffix) {
|
|
||||||
const s = n >= 0 ? "+" : "";
|
|
||||||
return s + n.toFixed(1) + (suffix || "");
|
|
||||||
}
|
|
||||||
|
|
||||||
traitStyle(trait) {
|
|
||||||
const map = {
|
|
||||||
"Arbitrage Bot": { bg: "rgba(22,82,240,0.14)", color: "#5b9dff", border: "rgba(22,82,240,0.3)" },
|
|
||||||
"Resolution Farming": { bg: "rgba(246,70,93,0.12)", color: "#F6465D", border: "rgba(246,70,93,0.28)" },
|
|
||||||
"Trading Bot": { bg: "rgba(255,255,255,0.08)", color: "#C7CCDA", border: "rgba(255,255,255,0.15)" },
|
|
||||||
"Human": { bg: "rgba(124,92,255,0.12)", color: "#a78bff", border: "rgba(124,92,255,0.28)" },
|
|
||||||
"High Volume": { bg: "rgba(18,212,138,0.12)", color: "#12D48A", border: "rgba(18,212,138,0.28)" },
|
|
||||||
};
|
|
||||||
return map[trait] || { bg: "rgba(255,255,255,0.08)", color: "#C7CCDA", border: "rgba(255,255,255,0.15)" };
|
|
||||||
}
|
|
||||||
|
|
||||||
ALL_TRAITS = ["Human", "Trading Bot", "Arbitrage Bot", "Resolution Farming", "High Volume"];
|
|
||||||
|
|
||||||
avatarBg(seed) {
|
|
||||||
const palettes = [
|
|
||||||
"linear-gradient(135deg,#1652F0,#4c8dff)",
|
|
||||||
"linear-gradient(135deg,#12D48A,#0a8f5f)",
|
|
||||||
"linear-gradient(135deg,#F6465D,#a8283a)",
|
|
||||||
"linear-gradient(135deg,#7c5cff,#4c8dff)",
|
|
||||||
"linear-gradient(135deg,#ff9d4c,#F6465D)",
|
|
||||||
];
|
|
||||||
let h = 0;
|
|
||||||
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
|
|
||||||
return palettes[h % palettes.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
buildDualSlider(minKey, maxKey, lo, hi, fmt) {
|
|
||||||
const minVal = this.state[minKey];
|
|
||||||
const maxVal = this.state[maxKey];
|
|
||||||
const pctMin = ((minVal - lo) / (hi - lo)) * 100;
|
|
||||||
const pctMax = ((maxVal - lo) / (hi - lo)) * 100;
|
|
||||||
return {
|
|
||||||
lo, hi, minVal, maxVal,
|
|
||||||
minLabel: fmt(minVal), maxLabel: fmt(maxVal),
|
|
||||||
fillLeft: pctMin.toFixed(1) + "%",
|
|
||||||
fillRight: (100 - pctMax).toFixed(1) + "%",
|
|
||||||
onMinInput: (e) => this.setState({ [minKey]: Math.min(Number(e.target.value), this.state[maxKey]) }),
|
|
||||||
onMaxInput: (e) => this.setState({ [maxKey]: Math.max(Number(e.target.value), this.state[minKey]) }),
|
|
||||||
onMinTextBlur: this.onMinTextBlur(minKey, lo, hi),
|
|
||||||
onMaxTextBlur: this.onMaxTextBlur(maxKey, lo, hi),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
buildPath(series, w, h, pad) {
|
|
||||||
if (!series || series.length < 2) return { line: "", area: "" };
|
|
||||||
const min = Math.min(...series);
|
|
||||||
const max = Math.max(...series);
|
|
||||||
const range = max - min || 1;
|
|
||||||
const innerW = w - pad * 2;
|
|
||||||
const innerH = h - pad * 2;
|
|
||||||
const pts = series.map((v, i) => {
|
|
||||||
const x = pad + (i / (series.length - 1)) * innerW;
|
|
||||||
const y = pad + innerH - ((v - min) / range) * innerH;
|
|
||||||
return [x, y];
|
|
||||||
});
|
|
||||||
let line = "M" + pts[0][0].toFixed(1) + "," + pts[0][1].toFixed(1);
|
|
||||||
for (let i = 1; i < pts.length; i++) line += " L" + pts[i][0].toFixed(1) + "," + pts[i][1].toFixed(1);
|
|
||||||
const area = line + ` L${pts[pts.length - 1][0].toFixed(1)},${h - pad} L${pts[0][0].toFixed(1)},${h - pad} Z`;
|
|
||||||
return { line, area };
|
|
||||||
}
|
|
||||||
|
|
||||||
renderVals() {
|
|
||||||
const markets = this.state.markets || [];
|
|
||||||
const traders = this.state.traders || [];
|
|
||||||
const view = this.state.view;
|
|
||||||
|
|
||||||
const navDefs = [
|
|
||||||
{ key: "overview", label: "Markets" },
|
|
||||||
{ key: "traders", label: "Traders" },
|
|
||||||
{ key: "watchlist", label: "Watchlist" },
|
|
||||||
{ key: "settings", label: "Settings" },
|
|
||||||
];
|
|
||||||
const navItems = navDefs.map((n) => {
|
|
||||||
const active = (n.key === "overview" && view === "overview") ||
|
|
||||||
(n.key === "traders" && (view === "trader" || view === "traderSearch")) ||
|
|
||||||
(n.key === "watchlist" && false) ||
|
|
||||||
(n.key === "settings" && false);
|
|
||||||
return {
|
|
||||||
label: n.label,
|
|
||||||
onClick: n.key === "overview" ? this.goOverview : (n.key === "traders" ? this.goTraderSearch : this.goOverview),
|
|
||||||
style: `display:flex; align-items:center; gap:10px; padding:10px 12px; border-radius:10px; font-size:13.5px; font-weight:600; cursor:pointer; color:${active ? "#EDEFF5" : "#8B93A7"}; background:${active ? "rgba(22,82,240,0.14)" : "transparent"};`,
|
|
||||||
dotStyle: `width:6px; height:6px; border-radius:50%; background:${active ? "#5b9dff" : "#3a4152"};`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---- global stats
|
|
||||||
const totalVol24 = markets.reduce((s, m) => s + m.volume24h, 0);
|
|
||||||
const openMarkets = markets.filter((m) => m.status === "Active").length;
|
|
||||||
const totalTraders = new Set(traders.map((t) => t.id)).size + markets.reduce((s, m) => s + m.traders, 0) * 0; // display sum below
|
|
||||||
const totalActiveTraders = markets.reduce((s, m) => s + m.traders, 0);
|
|
||||||
const avgLiquidity = markets.length ? markets.reduce((s, m) => s + m.liquidity, 0) / markets.length : 0;
|
|
||||||
|
|
||||||
const globalStats = [
|
|
||||||
{ label: "24H Volume", value: this.fmtMoney(totalVol24), delta: this.fmtSigned(3.4, "%"), deltaColor: "#12D48A" },
|
|
||||||
{ label: "Open Markets", value: String(openMarkets), delta: "+2 today", deltaColor: "#12D48A" },
|
|
||||||
{ label: "Active Traders", value: this.fmtNum(totalActiveTraders), delta: this.fmtSigned(1.8, "%"), deltaColor: "#12D48A" },
|
|
||||||
{ label: "Avg. Liquidity", value: this.fmtMoney(avgLiquidity), delta: this.fmtSigned(-0.6, "%"), deltaColor: "#F6465D" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const marketRows = markets.map((m) => ({
|
|
||||||
question: m.question,
|
|
||||||
category: m.category,
|
|
||||||
statusLabel: m.status,
|
|
||||||
yesPriceLabel: m.yesPrice.toFixed(1) + "%",
|
|
||||||
changeLabel: this.fmtSigned(m.change24h, "%"),
|
|
||||||
changeColor: m.change24h >= 0 ? "#12D48A" : "#F6465D",
|
|
||||||
volumeLabel: this.fmtMoney(m.volume24h),
|
|
||||||
tradersLabel: this.fmtNum(m.traders),
|
|
||||||
onClick: () => this.goMarket(m.id),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const leaderboard = [...traders].sort((a, b) => (b.pnlRealized + b.pnlUnrealized) - (a.pnlRealized + a.pnlUnrealized)).slice(0, 6).map((t) => ({
|
|
||||||
handle: t.handle,
|
|
||||||
trait: t.traits[0],
|
|
||||||
initial: t.handle[0].toUpperCase(),
|
|
||||||
avatarBg: this.avatarBg(t.id),
|
|
||||||
pnlLabel: this.fmtMoney(t.pnlRealized + t.pnlUnrealized),
|
|
||||||
onClick: () => this.goTrader(t.id),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const headerVolume = this.fmtMoney(totalVol24);
|
|
||||||
const crumbDefs = view === "overview" ? [["Markets Overview", null]] :
|
|
||||||
view === "market" ? [["Markets Overview", this.goOverview], ["Market Detail", null]] :
|
|
||||||
view === "traderSearch" ? [["Trader Search", null]] :
|
|
||||||
[["Trader Search", this.goTraderSearch], ["Trader Detail", null]];
|
|
||||||
const breadcrumbItems = crumbDefs.map(([label, onClick], i) => ({
|
|
||||||
label, onClick,
|
|
||||||
style: onClick ? "cursor:pointer; color:#8B93A7;" : "color:#EDEFF5; cursor:default;",
|
|
||||||
sep: i < crumbDefs.length - 1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// ---- trader search
|
|
||||||
let searchResults = [];
|
|
||||||
let traitFilterChips = [];
|
|
||||||
if (view === "traderSearch") {
|
|
||||||
const q = this.state.searchText.trim().toLowerCase();
|
|
||||||
traitFilterChips = this.ALL_TRAITS.map((tr) => {
|
|
||||||
const active = this.state.filterTraits.includes(tr);
|
|
||||||
const s = this.traitStyle(tr);
|
|
||||||
return {
|
|
||||||
label: tr,
|
|
||||||
onClick: () => this.toggleTrait(tr),
|
|
||||||
style: `cursor:pointer; font-size:12px; font-weight:700; padding:7px 13px; border-radius:9px; background:${active ? s.bg : "rgba(255,255,255,0.04)"}; color:${active ? s.color : "#8B93A7"}; border:1px solid ${active ? s.border : "rgba(255,255,255,0.09)"};`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
searchResults = traders.filter((t) => {
|
|
||||||
if (q && !t.handle.toLowerCase().includes(q) && !t.wallet.toLowerCase().includes(q)) return false;
|
|
||||||
if (this.state.filterTraits.length && !this.state.filterTraits.every((tr) => t.traits.includes(tr))) return false;
|
|
||||||
if (t.winRate < this.state.winRateMin || t.winRate > this.state.winRateMax) return false;
|
|
||||||
if (t.volume < this.state.volumeMin || t.volume > this.state.volumeMax) return false;
|
|
||||||
const totalPnl = t.pnlRealized + t.pnlUnrealized;
|
|
||||||
if (totalPnl < this.state.pnlMin || totalPnl > this.state.pnlMax) return false;
|
|
||||||
return true;
|
|
||||||
}).map((t) => ({
|
|
||||||
id: t.id,
|
|
||||||
handle: t.handle,
|
|
||||||
initial: t.handle[0].toUpperCase(),
|
|
||||||
avatarBg: this.avatarBg(t.id),
|
|
||||||
traitChips: t.traits.map((tr) => {
|
|
||||||
const s = this.traitStyle(tr);
|
|
||||||
return { label: tr, bg: s.bg, color: s.color, border: s.border };
|
|
||||||
}),
|
|
||||||
winRateLabel: t.winRate.toFixed(1) + "%",
|
|
||||||
volumeLabel: this.fmtMoney(t.volume),
|
|
||||||
pnlLabel: ((t.pnlRealized + t.pnlUnrealized) >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(t.pnlRealized + t.pnlUnrealized)),
|
|
||||||
pnlColor: (t.pnlRealized + t.pnlUnrealized) >= 0 ? "#12D48A" : "#F6465D",
|
|
||||||
onClick: () => this.goTrader(t.id),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- market detail
|
|
||||||
let marketDetail = null;
|
|
||||||
let marketStats = [];
|
|
||||||
if (view === "market") {
|
|
||||||
const m = markets.find((x) => x.id === this.state.selectedMarketId) || markets[0];
|
|
||||||
if (m) {
|
|
||||||
const { line, area } = this.buildPath(m.yesSeries, 600, 220, 14);
|
|
||||||
const maxBookSize = Math.max(1, ...m.orderBook.bids.map((b) => b[1]), ...m.orderBook.asks.map((a) => a[1]));
|
|
||||||
marketDetail = {
|
|
||||||
question: m.question,
|
|
||||||
category: m.category,
|
|
||||||
status: m.status,
|
|
||||||
statusBg: m.status === "Active" ? "rgba(18,212,138,0.12)" : "rgba(255,255,255,0.08)",
|
|
||||||
statusColor: m.status === "Active" ? "#12D48A" : "#8B93A7",
|
|
||||||
resolves: m.resolves,
|
|
||||||
yesPriceBig: m.yesPrice.toFixed(1) + "%",
|
|
||||||
linePath: line,
|
|
||||||
areaPath: area,
|
|
||||||
asks: [...m.orderBook.asks].reverse().map(([p, s]) => ({ priceLabel: p.toFixed(1) + "%", sizeLabel: this.fmtMoney(s), barWidth: Math.round((s / maxBookSize) * 100) + "%" })),
|
|
||||||
bids: m.orderBook.bids.map(([p, s]) => ({ priceLabel: p.toFixed(1) + "%", sizeLabel: this.fmtMoney(s), barWidth: Math.round((s / maxBookSize) * 100) + "%" })),
|
|
||||||
holders: m.holders.map((h) => ({
|
|
||||||
trader: h.trader,
|
|
||||||
side: h.side,
|
|
||||||
sideBg: h.side === "Yes" ? "rgba(18,212,138,0.14)" : "rgba(246,70,93,0.14)",
|
|
||||||
sideColor: h.side === "Yes" ? "#12D48A" : "#F6465D",
|
|
||||||
valueLabel: this.fmtMoney(h.value),
|
|
||||||
onClick: () => {
|
|
||||||
const t = traders.find((tr) => tr.handle === h.trader || tr.wallet === h.trader);
|
|
||||||
if (t) this.goTrader(t.id);
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
topTraders: m.topTraders.map((handle) => {
|
|
||||||
const t = traders.find((tr) => tr.handle === handle);
|
|
||||||
return t ? {
|
|
||||||
handle: t.handle,
|
|
||||||
trait: t.traits[0],
|
|
||||||
initial: t.handle[0].toUpperCase(),
|
|
||||||
avatarBg: this.avatarBg(t.id),
|
|
||||||
onClick: () => this.goTrader(t.id),
|
|
||||||
} : { handle, trait: "", initial: handle[0].toUpperCase(), avatarBg: "#333", onClick: () => {} };
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
marketStats = [
|
|
||||||
{ label: "24H Volume", value: this.fmtMoney(m.volume24h) },
|
|
||||||
{ label: "Total Volume", value: this.fmtMoney(m.volumeTotal) },
|
|
||||||
{ label: "Liquidity", value: this.fmtMoney(m.liquidity) },
|
|
||||||
{ label: "Open Interest", value: this.fmtMoney(m.openInterest) },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- trader detail
|
|
||||||
let traderDetail = null;
|
|
||||||
let traderStats = [];
|
|
||||||
if (view === "trader") {
|
|
||||||
const t = traders.find((x) => x.id === this.state.selectedTraderId) || traders[0];
|
|
||||||
if (t) {
|
|
||||||
const { line, area } = this.buildPath(t.pnlSeries, 600, 200, 14);
|
|
||||||
const totalPnl = t.pnlRealized + t.pnlUnrealized;
|
|
||||||
traderDetail = {
|
|
||||||
handle: t.handle,
|
|
||||||
wallet: t.wallet,
|
|
||||||
initial: t.handle[0].toUpperCase(),
|
|
||||||
avatarBg: this.avatarBg(t.id),
|
|
||||||
joined: t.joined,
|
|
||||||
activePositionsLabel: String(t.activePositions),
|
|
||||||
traitChips: t.traits.map((tr) => {
|
|
||||||
const s = this.traitStyle(tr);
|
|
||||||
return { label: tr, bg: s.bg, color: s.color, border: s.border };
|
|
||||||
}),
|
|
||||||
linePath: line,
|
|
||||||
areaPath: area,
|
|
||||||
hasConnections: t.connectedWallets.length > 0,
|
|
||||||
noConnections: t.connectedWallets.length === 0,
|
|
||||||
connections: t.connectedWallets,
|
|
||||||
positions: t.positions.map((p) => ({
|
|
||||||
market: p.market,
|
|
||||||
side: p.side,
|
|
||||||
sideBg: p.side === "Yes" ? "rgba(18,212,138,0.14)" : "rgba(246,70,93,0.14)",
|
|
||||||
sideColor: p.side === "Yes" ? "#12D48A" : "#F6465D",
|
|
||||||
sizeLabel: this.fmtMoney(p.size),
|
|
||||||
entryLabel: p.entry.toFixed(1) + "%",
|
|
||||||
currentLabel: p.current.toFixed(1) + "%",
|
|
||||||
pnlLabel: this.fmtSigned(0, "").slice(0,0) + (p.pnl >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(p.pnl)),
|
|
||||||
pnlColor: p.pnl >= 0 ? "#12D48A" : "#F6465D",
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
traderStats = [
|
|
||||||
{ label: "Realized P&L", value: (t.pnlRealized >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(t.pnlRealized)), color: t.pnlRealized >= 0 ? "#12D48A" : "#F6465D" },
|
|
||||||
{ label: "Unrealized P&L", value: (t.pnlUnrealized >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(t.pnlUnrealized)), color: t.pnlUnrealized >= 0 ? "#12D48A" : "#F6465D" },
|
|
||||||
{ label: "Win Rate", value: t.winRate.toFixed(1) + "%", color: "#EDEFF5" },
|
|
||||||
{ label: "Total Volume", value: this.fmtMoney(t.volume), color: "#EDEFF5" },
|
|
||||||
{ label: "Avg Position", value: this.fmtMoney(t.avgPositionSize), color: "#EDEFF5" },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
navItems,
|
|
||||||
headerVolume,
|
|
||||||
breadcrumbItems,
|
|
||||||
isOverview: view === "overview",
|
|
||||||
isMarket: view === "market",
|
|
||||||
isTrader: view === "trader",
|
|
||||||
isTraderSearch: view === "traderSearch",
|
|
||||||
searchResultsCount: String(searchResults.length),
|
|
||||||
searchResults,
|
|
||||||
traitFilterChips,
|
|
||||||
searchText: this.state.searchText,
|
|
||||||
onSearchTextChange: this.setSearchText,
|
|
||||||
winRateSlider: this.buildDualSlider("winRateMin", "winRateMax", 0, 100, (v) => v.toFixed(0) + "%"),
|
|
||||||
volumeSlider: this.buildDualSlider("volumeMin", "volumeMax", 0, 25000000, (v) => this.fmtMoney(v)),
|
|
||||||
pnlSlider: this.buildDualSlider("pnlMin", "pnlMax", -50000, 500000, (v) => (v >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(v))),
|
|
||||||
onResetFilters: this.resetFilters,
|
|
||||||
globalStats,
|
|
||||||
marketRows,
|
|
||||||
leaderboard,
|
|
||||||
marketDetail,
|
|
||||||
marketStats,
|
|
||||||
traderDetail,
|
|
||||||
traderStats,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
// Mock data for prediction market analytics prototype
|
|
||||||
|
|
||||||
function genSeries(n, start, vol, drift) {
|
|
||||||
const pts = [start];
|
|
||||||
for (let i = 1; i < n; i++) {
|
|
||||||
const next = Math.max(2, Math.min(98, pts[i - 1] + (Math.random() - 0.5 + drift) * vol));
|
|
||||||
pts.push(Math.round(next * 10) / 10);
|
|
||||||
}
|
|
||||||
return pts;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MARKETS = [
|
|
||||||
{
|
|
||||||
id: "fed-sep-cut",
|
|
||||||
question: "Fed cuts rates at September meeting?",
|
|
||||||
category: "Economics",
|
|
||||||
status: "Active",
|
|
||||||
resolves: "Sep 18, 2026",
|
|
||||||
yesPrice: 71.4,
|
|
||||||
change24h: 4.2,
|
|
||||||
volume24h: 2840000,
|
|
||||||
volumeTotal: 41200000,
|
|
||||||
liquidity: 6120000,
|
|
||||||
openInterest: 18400000,
|
|
||||||
traders: 8340,
|
|
||||||
yesSeries: genSeries(40, 55, 4, 0.06),
|
|
||||||
noSeries: null,
|
|
||||||
orderBook: {
|
|
||||||
bids: [[71.0, 182000], [70.5, 240000], [70.0, 310000], [69.5, 198000], [69.0, 145000]],
|
|
||||||
asks: [[71.5, 165000], [72.0, 221000], [72.5, 290000], [73.0, 176000], [73.5, 130000]]
|
|
||||||
},
|
|
||||||
holders: [
|
|
||||||
{ trader: "0xA13f…9c2", side: "Yes", size: 420000, value: 300000 },
|
|
||||||
{ trader: "quant_owl", side: "Yes", size: 318000, value: 227000 },
|
|
||||||
{ trader: "0x77Bd…41a", side: "No", size: 275000, value: 78600 },
|
|
||||||
{ trader: "resolvr", side: "Yes", size: 210000, value: 150000 },
|
|
||||||
{ trader: "0x9F02…c88", side: "No", size: 188000, value: 53700 }
|
|
||||||
],
|
|
||||||
topTraders: ["quant_owl", "resolvr", "arb_meridian", "0xA13f…9c2"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "btc-150k",
|
|
||||||
question: "Bitcoin above $150,000 by year end?",
|
|
||||||
category: "Crypto",
|
|
||||||
status: "Active",
|
|
||||||
resolves: "Dec 31, 2026",
|
|
||||||
yesPrice: 38.2,
|
|
||||||
change24h: -2.8,
|
|
||||||
volume24h: 5120000,
|
|
||||||
volumeTotal: 88900000,
|
|
||||||
liquidity: 11400000,
|
|
||||||
openInterest: 34200000,
|
|
||||||
traders: 21300,
|
|
||||||
yesSeries: genSeries(40, 45, 5, -0.02),
|
|
||||||
noSeries: null,
|
|
||||||
orderBook: {
|
|
||||||
bids: [[38.0, 410000], [37.5, 380000], [37.0, 512000], [36.5, 290000], [36.0, 210000]],
|
|
||||||
asks: [[38.5, 390000], [39.0, 460000], [39.5, 330000], [40.0, 280000], [40.5, 190000]]
|
|
||||||
},
|
|
||||||
holders: [
|
|
||||||
{ trader: "satoshi_fan", side: "Yes", size: 890000, value: 340000 },
|
|
||||||
{ trader: "0x4Ac1…d02", side: "No", size: 720000, value: 445000 },
|
|
||||||
{ trader: "bot_delta_9", side: "No", size: 610000, value: 377000 },
|
|
||||||
{ trader: "arb_meridian", side: "Yes", size: 540000, value: 206000 },
|
|
||||||
{ trader: "0xE221…7b4", side: "Yes", size: 480000, value: 183000 }
|
|
||||||
],
|
|
||||||
topTraders: ["satoshi_fan", "bot_delta_9", "arb_meridian", "0x4Ac1…d02"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ucl-winner",
|
|
||||||
question: "Real Madrid win Champions League 2027?",
|
|
||||||
category: "Sports",
|
|
||||||
status: "Active",
|
|
||||||
resolves: "May 30, 2027",
|
|
||||||
yesPrice: 22.6,
|
|
||||||
change24h: 1.1,
|
|
||||||
volume24h: 980000,
|
|
||||||
volumeTotal: 14300000,
|
|
||||||
liquidity: 3210000,
|
|
||||||
openInterest: 7600000,
|
|
||||||
traders: 5120,
|
|
||||||
yesSeries: genSeries(40, 20, 3, 0.03),
|
|
||||||
noSeries: null,
|
|
||||||
orderBook: {
|
|
||||||
bids: [[22.0, 92000], [21.5, 110000], [21.0, 88000], [20.5, 64000], [20.0, 51000]],
|
|
||||||
asks: [[22.5, 87000], [23.0, 101000], [23.5, 76000], [24.0, 55000], [24.5, 40000]]
|
|
||||||
},
|
|
||||||
holders: [
|
|
||||||
{ trader: "resolvr", side: "Yes", size: 210000, value: 47000 },
|
|
||||||
{ trader: "0xB901…5e3", side: "No", size: 198000, value: 153000 },
|
|
||||||
{ trader: "quant_owl", side: "Yes", size: 155000, value: 35000 },
|
|
||||||
{ trader: "farmer_jo", side: "Yes", size: 121000, value: 27000 }
|
|
||||||
],
|
|
||||||
topTraders: ["resolvr", "quant_owl", "0xB901…5e3"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "us-election-28",
|
|
||||||
question: "Democratic candidate wins 2028 election?",
|
|
||||||
category: "Politics",
|
|
||||||
status: "Active",
|
|
||||||
resolves: "Nov 7, 2028",
|
|
||||||
yesPrice: 54.8,
|
|
||||||
change24h: 0.6,
|
|
||||||
volume24h: 3620000,
|
|
||||||
volumeTotal: 61500000,
|
|
||||||
liquidity: 9870000,
|
|
||||||
openInterest: 28100000,
|
|
||||||
traders: 14700,
|
|
||||||
yesSeries: genSeries(40, 50, 3.5, 0.01),
|
|
||||||
noSeries: null,
|
|
||||||
orderBook: {
|
|
||||||
bids: [[54.5, 260000], [54.0, 310000], [53.5, 220000], [53.0, 190000], [52.5, 140000]],
|
|
||||||
asks: [[55.0, 245000], [55.5, 300000], [56.0, 210000], [56.5, 175000], [57.0, 120000]]
|
|
||||||
},
|
|
||||||
holders: [
|
|
||||||
{ trader: "0x2Ff8…a10", side: "Yes", size: 610000, value: 334000 },
|
|
||||||
{ trader: "arb_meridian", side: "No", size: 540000, value: 244000 },
|
|
||||||
{ trader: "night_owl_88", side: "Yes", size: 410000, value: 224000 },
|
|
||||||
{ trader: "0xC450…9d1", side: "Yes", size: 380000, value: 208000 }
|
|
||||||
],
|
|
||||||
topTraders: ["0x2Ff8…a10", "arb_meridian", "night_owl_88"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "openai-ipo",
|
|
||||||
question: "OpenAI IPOs before end of 2027?",
|
|
||||||
category: "Business",
|
|
||||||
status: "Active",
|
|
||||||
resolves: "Dec 31, 2027",
|
|
||||||
yesPrice: 16.9,
|
|
||||||
change24h: -0.4,
|
|
||||||
volume24h: 740000,
|
|
||||||
volumeTotal: 9800000,
|
|
||||||
liquidity: 2140000,
|
|
||||||
openInterest: 5300000,
|
|
||||||
traders: 3980,
|
|
||||||
yesSeries: genSeries(40, 18, 2.5, -0.01),
|
|
||||||
noSeries: null,
|
|
||||||
orderBook: {
|
|
||||||
bids: [[16.5, 61000], [16.0, 72000], [15.5, 58000], [15.0, 40000], [14.5, 33000]],
|
|
||||||
asks: [[17.0, 55000], [17.5, 63000], [18.0, 47000], [18.5, 36000], [19.0, 28000]]
|
|
||||||
},
|
|
||||||
holders: [
|
|
||||||
{ trader: "bot_delta_9", side: "No", size: 310000, value: 258000 },
|
|
||||||
{ trader: "0x9F02…c88", side: "Yes", size: 190000, value: 32000 },
|
|
||||||
{ trader: "farmer_jo", side: "No", size: 140000, value: 116000 }
|
|
||||||
],
|
|
||||||
topTraders: ["bot_delta_9", "0x9F02…c88"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "fifa-worldcup",
|
|
||||||
question: "Brazil wins 2026 FIFA World Cup?",
|
|
||||||
category: "Sports",
|
|
||||||
status: "Resolved · No",
|
|
||||||
resolves: "Jul 19, 2026",
|
|
||||||
yesPrice: 0,
|
|
||||||
change24h: 0,
|
|
||||||
volume24h: 0,
|
|
||||||
volumeTotal: 27600000,
|
|
||||||
liquidity: 0,
|
|
||||||
openInterest: 0,
|
|
||||||
traders: 19200,
|
|
||||||
yesSeries: genSeries(40, 30, 5, 0.1).map((v,i,a)=> i>34 ? Math.max(0, 30 - (i-34)*6) : v),
|
|
||||||
noSeries: null,
|
|
||||||
orderBook: { bids: [], asks: [] },
|
|
||||||
holders: [
|
|
||||||
{ trader: "night_owl_88", side: "No", size: 520000, value: 520000 },
|
|
||||||
{ trader: "0xC450…9d1", side: "Yes", size: 300000, value: 0 }
|
|
||||||
],
|
|
||||||
topTraders: ["night_owl_88", "0xC450…9d1"]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export const TRADERS = [
|
|
||||||
{
|
|
||||||
id: "quant_owl",
|
|
||||||
handle: "quant_owl",
|
|
||||||
wallet: "0x1a4F…88bC",
|
|
||||||
traits: ["Arbitrage Bot", "High Volume"],
|
|
||||||
joined: "Mar 2024",
|
|
||||||
pnlRealized: 184200,
|
|
||||||
pnlUnrealized: 32100,
|
|
||||||
winRate: 68.4,
|
|
||||||
volume: 4820000,
|
|
||||||
avgPositionSize: 41200,
|
|
||||||
activePositions: 14,
|
|
||||||
pnlSeries: genSeries(30, 40, 6, 0.08),
|
|
||||||
positions: [
|
|
||||||
{ market: "Fed cuts rates at September meeting?", side: "Yes", size: 318000, entry: 62.1, current: 71.4, pnl: 29580, status: "Open" },
|
|
||||||
{ market: "Real Madrid win Champions League 2027?", side: "Yes", size: 155000, entry: 18.4, current: 22.6, pnl: 6510, status: "Open" },
|
|
||||||
{ market: "US Presidential Election 2028", side: "No", size: 96000, entry: 48.2, current: 45.2, pnl: 2880, status: "Open" },
|
|
||||||
{ market: "Bitcoin above $100k Jan 2026", side: "Yes", size: 210000, entry: 55.0, current: 100, pnl: 94500, status: "Resolved" }
|
|
||||||
],
|
|
||||||
connectedWallets: ["0x88Ac…2f01", "0x5b0D…e410"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bot_delta_9",
|
|
||||||
handle: "bot_delta_9",
|
|
||||||
wallet: "0x5C90…14fA",
|
|
||||||
traits: ["Trading Bot", "Resolution Farming"],
|
|
||||||
joined: "Jan 2025",
|
|
||||||
pnlRealized: 61400,
|
|
||||||
pnlUnrealized: -8200,
|
|
||||||
winRate: 54.1,
|
|
||||||
volume: 12400000,
|
|
||||||
avgPositionSize: 18300,
|
|
||||||
activePositions: 62,
|
|
||||||
pnlSeries: genSeries(30, 30, 3, 0.02),
|
|
||||||
positions: [
|
|
||||||
{ market: "Bitcoin above $150,000 by year end?", side: "No", size: 610000, entry: 41.0, current: 38.2, pnl: 17080, status: "Open" },
|
|
||||||
{ market: "OpenAI IPOs before end of 2027?", side: "No", size: 310000, entry: 84.2, current: 83.1, pnl: 3410, status: "Open" },
|
|
||||||
{ market: "Brazil wins 2026 FIFA World Cup?", side: "No", size: 520000, entry: 71.0, current: 100, pnl: 150800, status: "Resolved" }
|
|
||||||
],
|
|
||||||
connectedWallets: ["0x14fA…C90D", "0x77b2…9e11", "0x0021…aabc"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "arb_meridian",
|
|
||||||
handle: "arb_meridian",
|
|
||||||
wallet: "0x902D…7Ac1",
|
|
||||||
traits: ["Arbitrage Bot"],
|
|
||||||
joined: "Aug 2023",
|
|
||||||
pnlRealized: 402800,
|
|
||||||
pnlUnrealized: 51200,
|
|
||||||
winRate: 71.9,
|
|
||||||
volume: 22100000,
|
|
||||||
avgPositionSize: 68400,
|
|
||||||
activePositions: 9,
|
|
||||||
pnlSeries: genSeries(30, 60, 5, 0.05),
|
|
||||||
positions: [
|
|
||||||
{ market: "Bitcoin above $150,000 by year end?", side: "Yes", size: 540000, entry: 30.5, current: 38.2, pnl: 41580, status: "Open" },
|
|
||||||
{ market: "Democratic candidate wins 2028 election?", side: "No", size: 540000, entry: 47.0, current: 45.2, pnl: 9720, status: "Open" },
|
|
||||||
{ market: "Fed cuts rates at September meeting?", side: "Yes", size: 180000, entry: 58.0, current: 71.4, pnl: 24120, status: "Open" }
|
|
||||||
],
|
|
||||||
connectedWallets: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "resolvr",
|
|
||||||
handle: "resolvr",
|
|
||||||
wallet: "0xF001…22Cd",
|
|
||||||
traits: ["Human", "Resolution Farming"],
|
|
||||||
joined: "Nov 2024",
|
|
||||||
pnlRealized: 92300,
|
|
||||||
pnlUnrealized: 12800,
|
|
||||||
winRate: 59.7,
|
|
||||||
volume: 6740000,
|
|
||||||
avgPositionSize: 27600,
|
|
||||||
activePositions: 22,
|
|
||||||
pnlSeries: genSeries(30, 35, 4, 0.03),
|
|
||||||
positions: [
|
|
||||||
{ market: "Fed cuts rates at September meeting?", side: "Yes", size: 210000, entry: 60.0, current: 71.4, pnl: 23940, status: "Open" },
|
|
||||||
{ market: "Real Madrid win Champions League 2027?", side: "Yes", size: 210000, entry: 15.2, current: 22.6, pnl: 15540, status: "Open" }
|
|
||||||
],
|
|
||||||
connectedWallets: ["0x22Cd…F001"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "night_owl_88",
|
|
||||||
handle: "night_owl_88",
|
|
||||||
wallet: "0x3E77…B420",
|
|
||||||
traits: ["Human", "High Volume"],
|
|
||||||
pnlRealized: 128900,
|
|
||||||
pnlUnrealized: 4100,
|
|
||||||
winRate: 62.2,
|
|
||||||
volume: 9120000,
|
|
||||||
avgPositionSize: 35200,
|
|
||||||
activePositions: 11,
|
|
||||||
pnlSeries: genSeries(30, 42, 4, 0.04),
|
|
||||||
positions: [
|
|
||||||
{ market: "Democratic candidate wins 2028 election?", side: "Yes", size: 410000, entry: 51.0, current: 54.8, pnl: 15580, status: "Open" },
|
|
||||||
{ market: "Brazil wins 2026 FIFA World Cup?", side: "No", size: 520000, entry: 68.0, current: 100, pnl: 166400, status: "Resolved" }
|
|
||||||
],
|
|
||||||
connectedWallets: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "farmer_jo",
|
|
||||||
handle: "farmer_jo",
|
|
||||||
wallet: "0xA210…6Bf0",
|
|
||||||
traits: ["Human", "Resolution Farming"],
|
|
||||||
pnlRealized: 21400,
|
|
||||||
pnlUnrealized: -3100,
|
|
||||||
winRate: 48.9,
|
|
||||||
volume: 1840000,
|
|
||||||
avgPositionSize: 9200,
|
|
||||||
activePositions: 31,
|
|
||||||
pnlSeries: genSeries(30, 20, 3, -0.01),
|
|
||||||
positions: [
|
|
||||||
{ market: "Real Madrid win Champions League 2027?", side: "Yes", size: 121000, entry: 24.0, current: 22.6, pnl: -1690, status: "Open" },
|
|
||||||
{ market: "OpenAI IPOs before end of 2027?", side: "No", size: 140000, entry: 80.0, current: 83.1, pnl: -4340, status: "Open" }
|
|
||||||
],
|
|
||||||
connectedWallets: ["0x6Bf0…A210", "0x9021…4Ccb"]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 339 KiB |
Reference in New Issue
Block a user