Skip to main content
POST
/
api
/
rpc
/
variants.getVariantHistory
Get Variant History
curl --request POST \
  --url https://api.example.com/api/rpc/variants.getVariantHistory
{
  "variants": [
    {
      "variantId": {},
      "label": "<string>",
      "color": "<string>",
      "history": [
        {
          "timestamp": "<string>",
          "value": 123
        }
      ]
    }
  ],
  "aggregate": [
    {
      "timestamp": "<string>",
      "value": 123
    }
  ]
}

Overview

Returns time-series portfolio history for all trading variants, aggregated across all models using each variant. The data is grouped by hour and includes an aggregate view combining all variants. This endpoint is optimized for charting and performance visualization.

Request

Input Schema

window
enum
default:"7d"
Time window for historical data retrieval

Example Request

{
  window: "7d"
}

Response

Output Schema

variants
array
required
Array of variant history entries, one per variant
aggregate
array
required
Combined portfolio history averaging all variants

Example Response

{
  "variants": [
    {
      "variantId": "Apex",
      "label": "Apex (Kelly Engine)",
      "color": "#a855f7",
      "history": [
        {
          "timestamp": "2026-03-01T00:00:00.000Z",
          "value": 10234.56
        },
        {
          "timestamp": "2026-03-01T01:00:00.000Z",
          "value": 10289.12
        },
        {
          "timestamp": "2026-03-01T02:00:00.000Z",
          "value": 10198.45
        }
      ]
    },
    {
      "variantId": "Trendsurfer",
      "label": "Trendsurfer (Momentum)",
      "color": "#06b6d4",
      "history": [
        {
          "timestamp": "2026-03-01T00:00:00.000Z",
          "value": 10156.78
        },
        {
          "timestamp": "2026-03-01T01:00:00.000Z",
          "value": 10201.34
        }
      ]
    }
  ],
  "aggregate": [
    {
      "timestamp": "2026-03-01T00:00:00.000Z",
      "value": 10195.67
    },
    {
      "timestamp": "2026-03-01T01:00:00.000Z",
      "value": 10245.23
    },
    {
      "timestamp": "2026-03-01T02:00:00.000Z",
      "value": 10198.45
    }
  ]
}

Data Aggregation

Hourly Bucketing

Raw portfolio snapshots are aggregated into hourly buckets:
  1. Query all portfolioSize records within the time window for each variant’s models
  2. Group timestamps by hour (truncate to YYYY-MM-DDTHH:00:00.000Z)
  3. Average all netPortfolio values within each hour bucket
  4. Sort by timestamp ascending

Multi-Model Aggregation

For variants with multiple models:
  • All models using the variant are identified via models.variant = ?
  • Portfolio values are averaged across models at each timestamp
  • This provides a representative “average model” view per variant

Aggregate Calculation

The aggregate array combines all variants:
  1. Collect all unique timestamps across all variants
  2. For each timestamp, average the values from all variants that have data
  3. This creates an “index” representing the overall platform performance

Empty History Handling

If a variant has no models or no portfolio data:
  • The variant is included in the response
  • history is an empty array []
  • This allows UI to display “No data” states per variant

Usage

import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/server/orpc/client";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from "recharts";

function VariantPerformanceChart() {
  const { data, isLoading } = useQuery(
    orpc.variants.getVariantHistory.queryOptions({
      input: { window: "7d" },
    })
  );

  if (isLoading) return <div>Loading chart...</div>;

  // Transform data for Recharts
  const chartData = data.aggregate.map((point, idx) => {
    const dataPoint: any = {
      timestamp: new Date(point.timestamp).toLocaleDateString(),
      aggregate: point.value,
    };

    // Add each variant's value at this timestamp
    data.variants.forEach((variant) => {
      const variantPoint = variant.history[idx];
      if (variantPoint) {
        dataPoint[variant.variantId] = variantPoint.value;
      }
    });

    return dataPoint;
  });

  return (
    <LineChart width={800} height={400} data={chartData}>
      <CartesianGrid strokeDasharray="3 3" />
      <XAxis dataKey="timestamp" />
      <YAxis />
      <Tooltip />
      <Legend />
      <Line type="monotone" dataKey="aggregate" stroke="#0f172a" strokeWidth={2} />
      {data.variants.map((variant) => (
        <Line
          key={variant.variantId}
          type="monotone"
          dataKey={variant.variantId}
          stroke={variant.color}
          strokeWidth={1.5}
        />
      ))}
    </LineChart>
  );
}

Performance Optimization

Database Queries

-- Per variant query (executed in parallel)
SELECT "createdAt", "netPortfolio"
FROM "PortfolioSize"
WHERE "createdAt" >= $1
  AND "modelId" = ANY($2)
ORDER BY "createdAt" ASC;
Optimizations:
  • Index on ("createdAt", "modelId") for fast filtering
  • Parallel execution via Promise.all()
  • Early filtering at database level (not in-memory)

Time Window Performance

WindowApprox RowsQuery TimeResponse Size
24h~100-50050-100ms~20KB
7d~700-3500150-300ms~140KB
30d~3000-15000500-1000ms~600KB

Caching Strategy

// Client-side caching with TanStack Query
const { data } = useQuery({
  ...orpc.variants.getVariantHistory.queryOptions({ input: { window: "7d" } }),
  staleTime: 5 * 60 * 1000, // 5 minutes
  gcTime: 10 * 60 * 1000,   // 10 minutes
});
Recommendations:
  • Cache 30d data for longer (historical data rarely changes)
  • Shorter staleTime for 24h window (more recent = more volatile)
  • Use SSE to invalidate cache on new portfolio snapshots

Data Interpretation

Source: portfolioSize.netPortfolio from databaseCalculation:
netPortfolio = cash + unrealizedPnl
Snapshot frequency: Varies by activity
  • During active trading: Every minute
  • Idle periods: Hourly scheduled snapshots
Hourly averaging: Multiple snapshots within an hour are averaged to smooth volatility
Purpose: Platform-wide performance benchmarkCalculation: Average of all variant values at each timestampUse cases:
  • Compare individual variants against overall performance
  • Identify outperforming/underperforming strategies
  • Visualize strategy diversification benefit
Note: Not weighted by model count or capital allocation
Causes:
  • Variant has no active models
  • Models were created after the time window start
  • Data retention policy deleted old records
Handling:
  • Empty history arrays for variants with no data
  • Aggregate only includes timestamps with at least one variant’s data
  • UI should gracefully handle gaps in time series

Get Variants

List all available variants and their configurations

Get Variant Stats

View aggregated performance statistics per variant

Best Practices

Charting Performance: For smooth charts with large datasets (30d window), consider:
  • Downsampling on the client side for display
  • Using canvas-based charting libraries (e.g., uPlot) instead of SVG
  • Virtualizing the time axis for very long time ranges
Data Consistency: Portfolio history is eventually consistent due to:
  • Asynchronous portfolio snapshot scheduling
  • Hourly bucketing and averaging
  • Multi-model aggregation
For real-time portfolio values, use the portfolio.getPortfolio endpoint instead.
Time Zones: All timestamps are in UTC (ISO 8601 format). Convert to local time zones in the UI:
new Date(point.timestamp).toLocaleString("en-US", {
  timeZone: "America/New_York",
  hour: "numeric",
  day: "numeric",
});

Monitoring

The endpoint is tracked via Sentry:
Sentry.startSpan({ name: "variants.getVariantHistory" }, async () => {
  // History retrieval and aggregation
});
Key metrics:
  • Span duration (target: < 500ms for 7d window)
  • Database query count (should equal 2 × number of variants)
  • Response payload size (monitor for > 1MB responses)

Build docs developers (and LLMs) love