Description
Retrieves historical portfolio net asset value (NAV) over time, grouped by model. Supports date range filtering and automatic downsampling to optimize chart rendering performance. Essential for displaying portfolio performance charts, calculating drawdowns, and analyzing historical returns.
Filter portfolio history by model variant. Valid values: "Apex", "Trendsurfer", "Contrarian", "Sovereign". Omit to fetch history from all variants.
ISO 8601 datetime string for the start of the date range (e.g., "2024-01-01T00:00:00.000Z"). Defaults to earliest available data if omitted.
ISO 8601 datetime string for the end of the date range (e.g., "2024-12-31T23:59:59.999Z"). Defaults to current time if omitted.
Maximum number of data points to return. Used for downsampling when dataset is large. Must be between 100 and 15,000. Defaults to an appropriate value based on the query (aggregate mode needs more points since data spans multiple model-variant combinations).
TypeScript Type
type PortfolioHistoryInput = {
variant ?: "Apex" | "Trendsurfer" | "Contrarian" | "Sovereign" ;
startDate ?: string ; // ISO 8601 datetime
endDate ?: string ; // ISO 8601 datetime
maxPoints ?: number ; // min: 100, max: 15000
};
Output Schema
Array of portfolio value snapshots over time. Show PortfolioSnapshot properties
Unique identifier for the portfolio snapshot (UUID).
Database ID of the model this snapshot belongs to.
Total portfolio value in USD as a string (e.g., "10234.56"). Stored as TEXT to preserve precision. Convert to number for calculations: parseFloat(netPortfolio).
ISO 8601 timestamp when this snapshot was first created.
ISO 8601 timestamp when this snapshot was last updated.
Information about the model that owns this portfolio. Show ModelInfo properties
Display name of the model (e.g., "gpt-4o", "claude-3-5-sonnet-20241022").
Variant classification: "Apex", "Trendsurfer", "Contrarian", or "Sovereign".
OpenRouter model identifier if applicable (e.g., "openai/gpt-4o").
resolution
DownsampleResolution
required
Time resolution of the returned data after downsampling. Indicates the time bucket size used:
"1m" - 1-minute intervals
"5m" - 5-minute intervals
"15m" - 15-minute intervals
"1h" - 1-hour intervals
"4h" - 4-hour intervals
The resolution is automatically determined based on the data range and maxPoints parameter.
TypeScript Type
type DownsampleResolution = "1m" | "5m" | "15m" | "1h" | "4h" ;
type PortfolioHistoryResponse = {
history : {
id : string ;
modelId : string ;
netPortfolio : string ;
createdAt : string ;
updatedAt : string ;
model ?: {
name : string ;
variant ?: "Apex" | "Trendsurfer" | "Contrarian" | "Sovereign" ;
openRouterModelName ?: string ;
};
}[];
resolution : DownsampleResolution ;
};
Example Usage
Basic Portfolio Chart
import { useQuery } from "@tanstack/react-query" ;
import { orpc } from "@/server/orpc/client" ;
import { LineChart , Line , XAxis , YAxis , CartesianGrid , Tooltip , Legend } from 'recharts' ;
function PortfolioChart () {
const { data , isLoading } = useQuery (
orpc . trading . getPortfolioHistory . queryOptions ({
input: {}
})
);
if ( isLoading ) return < div > Loading chart ...</ div > ;
// Transform data for chart
const chartData = data ?. history . map ( snapshot => ({
timestamp: new Date ( snapshot . createdAt ). getTime (),
value: parseFloat ( snapshot . netPortfolio ),
model: snapshot . model ?. name ?? 'Unknown' ,
})) ?? [];
return (
< div >
< h3 > Portfolio Performance </ h3 >
< p > Resolution : { data ?. resolution }</ p >
< LineChart width = { 800 } height = { 400 } data = { chartData } >
< CartesianGrid strokeDasharray = "3 3" />
< XAxis
dataKey = "timestamp"
tickFormatter = {(ts) => new Date ( ts ). toLocaleDateString ()}
/>
< YAxis
tickFormatter = {(value) => `$ ${ value . toLocaleString () } ` }
/>
< Tooltip
labelFormatter = {(ts) => new Date ( ts ). toLocaleString ()}
formatter = {(value) => [ `$ ${ Number ( value ). toFixed ( 2 ) } ` , 'Portfolio Value' ]}
/>
<Legend />
<Line type="monotone" dataKey="value" stroke="#8884d8" name="Portfolio" />
</LineChart>
</div>
);
}
Filter by Date Range
import { useQuery } from "@tanstack/react-query" ;
import { orpc } from "@/server/orpc/client" ;
import { useState } from "react" ;
function DateRangeChart () {
const [ startDate ] = useState (() => {
const date = new Date ();
date . setDate ( date . getDate () - 7 ); // Last 7 days
return date . toISOString ();
});
const { data } = useQuery (
orpc . trading . getPortfolioHistory . queryOptions ({
input: {
startDate ,
endDate: new Date (). toISOString (),
maxPoints: 500 ,
}
})
);
const totalReturn = data ?. history . length ?
parseFloat ( data . history [ data . history . length - 1 ]. netPortfolio ) - parseFloat ( data . history [ 0 ]. netPortfolio )
: 0 ;
const returnPct = data ?. history . length ?
( totalReturn / parseFloat ( data . history [ 0 ]. netPortfolio )) * 100
: 0 ;
return (
< div >
< h3 > Last 7 Days Performance </ h3 >
< p > Resolution : { data ?. resolution }</ p >
< p style = {{ color : totalReturn >= 0 ? 'green' : 'red' }} >
Return : $ {totalReturn.toFixed( 2 ) } ({returnPct >= 0 ? '+' : '' }{returnPct.toFixed( 2 ) } % )
</ p >
{ /* Chart component */ }
</ div >
);
}
Filter by Variant
import { useQuery } from "@tanstack/react-query" ;
import { orpc } from "@/server/orpc/client" ;
function ApexPerformance () {
const { data } = useQuery (
orpc . trading . getPortfolioHistory . queryOptions ({
input: {
variant: "Apex" ,
maxPoints: 1000 ,
}
})
);
// Group by model for multi-line chart
const seriesData = data ?. history . reduce (( acc , snapshot ) => {
const modelName = snapshot . model ?. name ?? 'Unknown' ;
if ( ! acc [ modelName ]) acc [ modelName ] = [];
acc [ modelName ]. push ({
timestamp: snapshot . createdAt ,
value: parseFloat ( snapshot . netPortfolio ),
});
return acc ;
}, {} as Record < string , Array <{ timestamp : string ; value : number }>>);
return (
< div >
< h3 > Apex Variant Performance </ h3 >
< p > Resolution : { data ?. resolution }</ p >
< p > Models tracked : { Object . keys ( seriesData ? ? {}). length }</ p >
{ /* Multi-line chart showing each Apex model */ }
</ div >
);
}
import { useQuery } from "@tanstack/react-query" ;
import { orpc } from "@/server/orpc/client" ;
function PerformanceMetrics () {
const { data } = useQuery (
orpc . trading . getPortfolioHistory . queryOptions ({
input: { maxPoints: 5000 }
})
);
if ( ! data ?. history . length ) return < div > No data available </ div > ;
const values = data . history . map ( s => parseFloat ( s . netPortfolio ));
const initialValue = values [ 0 ];
const currentValue = values [ values . length - 1 ];
const totalReturn = currentValue - initialValue ;
const returnPct = ( totalReturn / initialValue ) * 100 ;
// Calculate max drawdown
let peak = values [ 0 ];
let maxDrawdown = 0 ;
values . forEach ( value => {
if ( value > peak ) peak = value ;
const drawdown = (( value - peak ) / peak ) * 100 ;
if ( drawdown < maxDrawdown ) maxDrawdown = drawdown ;
});
// Calculate daily returns for Sharpe ratio
const dailyReturns : number [] = [];
for ( let i = 1 ; i < values . length ; i ++ ) {
const ret = ( values [ i ] - values [ i - 1 ]) / values [ i - 1 ];
dailyReturns . push ( ret );
}
const avgReturn = dailyReturns . reduce (( sum , r ) => sum + r , 0 ) / dailyReturns . length ;
const variance = dailyReturns . reduce (( sum , r ) => sum + Math . pow ( r - avgReturn , 2 ), 0 ) / dailyReturns . length ;
const stdDev = Math . sqrt ( variance );
const sharpe = stdDev !== 0 ? ( avgReturn / stdDev ) * Math . sqrt ( 252 ) : 0 ; // Annualized
return (
< div >
< h3 > Performance Metrics </ h3 >
< p > Resolution : { data . resolution }</ p >
< p > Data Points : { data . history . length }</ p >
< p > Initial Value : $ {initialValue.toFixed( 2 ) } </ p >
< p > Current Value : $ {currentValue.toFixed( 2 ) } </ p >
< p style = {{ color : totalReturn >= 0 ? 'green' : 'red' }} >
Total Return : $ {totalReturn.toFixed( 2 ) } ({returnPct >= 0 ? '+' : '' }{returnPct.toFixed( 2 ) } % )
</ p >
< p > Max Drawdown : { maxDrawdown . toFixed ( 2 )}%</ p >
< p > Sharpe Ratio : { sharpe . toFixed ( 2 )}</ p >
</ div >
);
}
Compare Multiple Variants
import { useQueries } from "@tanstack/react-query" ;
import { orpc } from "@/server/orpc/client" ;
function VariantComparison () {
const variants = [ "Apex" , "Trendsurfer" , "Contrarian" , "Sovereign" ] as const ;
const queries = useQueries ({
queries: variants . map ( variant =>
orpc . trading . getPortfolioHistory . queryOptions ({
input: { variant , maxPoints: 1000 }
})
),
});
const isLoading = queries . some ( q => q . isLoading );
if ( isLoading ) return < div > Loading comparison ...</ div > ;
return (
< div >
< h3 > Variant Performance Comparison </ h3 >
{ variants . map (( variant , idx ) => {
const data = queries [ idx ]. data ;
if (! data ? .history.length) return null;
const initialValue = parseFloat ( data . history [ 0 ]. netPortfolio );
const currentValue = parseFloat ( data . history [ data . history . length - 1 ]. netPortfolio );
const returnPct = (( currentValue - initialValue ) / initialValue ) * 100 ;
return (
< div key = { variant } >
< h4 >{ variant }</ h4 >
< p > Resolution : { data . resolution }</ p >
< p > Return : < span style = {{ color : returnPct > = 0 ? 'green' : 'red' }}>
{ returnPct >= 0 ? '+' : '' }{ returnPct . toFixed ( 2 )}%
</ span ></ p >
</ div >
);
})}
</ div >
);
}
Implementation Notes
Precision : netPortfolio is stored as TEXT in the database to preserve decimal precision. Always use parseFloat() for calculations.
Downsampling : Server automatically downsamples based on data range and maxPoints to optimize performance. The resolution field indicates the time bucket size used.
Retention Policy : Raw data is retained for 7 days, then aggregated into hourly buckets for 30 days. Older data is aggregated into daily buckets.
Aggregate Mode : When querying without a variant, data spans all model-variant combinations, requiring higher maxPoints for sufficient granularity.
Error Handling : Throws an error on fetch failure with the original error message.