TPO market profile
The built-in TS_MARKET_PROFILE indicator computes Time Price Opportunity profiles from the chart's OHLC bars. It does not require depth, time and sales, open interest, or a separate profile endpoint.
Data requirements
loadBars must return ascending bars with Unix-millisecond opening timestamps and truthful high and low values. The indicator groups those bars into UTC-anchored sessions, assigns each bar to a letter block, and stamps that letter across every touched price row.
Use an interval no larger than the configured blockMinutes. A 30-minute TPO period works directly with 30-minute or finer bars; coarser bars cannot reconstruct the missing letters.
Availability in the built-in UI
TPO appears in the Order Flow menu when the orderFlow feature is enabled and the customer's built-in-indicator catalog permits TS_MARKET_PROFILE. It remains available on a bars-only feed; no depth, tape, liquidation, or additional datafeed capability flag is required.
The Order Flow row treats every TS_MARKET_PROFILE instance on the chart as one menu-owned surface. Turning TPO on adds a default instance only when none exists. The settings button edits the first active instance, and turning TPO off removes every active TS_MARKET_PROFILE instance. Restored chart state remains the source of truth after reload, so the menu does not add a duplicate default profile.
If the product intentionally needs several independently managed TPO profiles, use addBuiltInIndicator, keep each returned instance id, and update or remove those instances through the chart API instead of using the aggregate Order Flow toggle.
Add the built-in indicator
import type { ChartApi } from '@tradescript/pro/sdk'
export async function addTpoMarketProfile(chart: ChartApi): Promise<string> {
return await chart.addBuiltInIndicator('TS_MARKET_PROFILE', {
inputs: {
sessionMinutes: 1440,
blockMinutes: 30,
rowSize: 0,
valueAreaRatio: 0.7,
rowsPerSession: 30,
initialBalanceBlocks: 2,
profileWidthPercent: 90,
showLetters: true,
showValueArea: true,
showPoc: true,
showNakedPoc: true,
showSinglePrints: false,
showInitialBalance: true,
},
styles: {
neutralColor: '#64748b',
valueAreaColor: '#a78bfa',
pocColor: '#f59e0b',
initialBalanceColor: '#38bdf8',
},
})
}
rowSize: 0 selects a stable automatic row size from the median high-to-low range of up to 20 recent UTC-anchored sessions in the loaded chart history, divided by rowsPerSession and rounded to a readable price step. It does not recalculate from the visible range when the user pans or zooms. Set a positive value when the product needs a fixed market-specific price row.
Main inputs
| Input | Default | Effect |
|---|---|---|
sessionMinutes | 1440 | Length of each UTC-anchored profile session |
blockMinutes | 30 | Time represented by one TPO letter |
rowSize | 0 | Price per profile row; zero selects automatic sizing |
valueAreaRatio | 0.7 | Share of TPO count included around the POC |
rowsPerSession | 30 | Target row count used only by automatic row sizing |
initialBalanceBlocks | 2 | Opening blocks used for initial balance |
profileWidthPercent | 90 | Maximum share of the session width available to the profile |
showLetters | true | Draw TPO letters when the complete session text fits |
showValueArea | true | Draw value-area rows and VAH/VAL guides |
showPoc | true | Draw the session point-of-control guide |
showNakedPoc | true | Extends POC until a later session trades through it |
showSinglePrints | false | Highlights internal rows touched by one letter only |
showInitialBalance | true | Draw the opening-block range |
showSessionRange | false | Draw each session's complete high-to-low range |
showLabels | false | Draw VAH, VAL, POC, initial-balance, and range labels when their guides are visible |
opacity | 0.85 | Profile opacity from 0.1..1 |
fontSize | 9 | TPO letter size in CSS pixels from 8..16 |
The input contract clamps blockMinutes to 1..1440, valueAreaRatio to
0.1..0.99, rowsPerSession to 8..200, and profileWidthPercent to
10..100. sessionMinutes and initialBalanceBlocks must be positive.
Main styles
| Style | Default | Effect |
|---|---|---|
neutralColor | #64748b | Rows outside the value area |
valueAreaColor | #a78bfa | Value-area rows and VAH/VAL guides |
pocColor | #f59e0b | POC row and guide |
initialBalanceColor | #38bdf8 | Initial-balance range |
singlePrintColor | #fb7185 | Single-print runs |
sessionRangeColor | #94a3b8 | Session high/low range |
textColor | #f8fafc | Ordinary TPO letters and labels |
pocTextColor | #0f172a | Letters painted on the POC row |
The built-in indicator reference lists every input and style key.
Update and remove the instance
Keep the returned instance id. It identifies this profile when the chart contains several indicators.
import type { ChartApi } from '@tradescript/pro/sdk'
export async function useTpoProfile(chart: ChartApi, instanceId: string): Promise<void> {
await chart.updateIndicator(instanceId, {
inputs: { blockMinutes: 15, showSinglePrints: true },
visible: true,
})
await chart.removeIndicator(instanceId)
}
Indicator instances are included in chart state and layouts. Symbol, interval, or bar-history changes recalculate each instance from the newly loaded bars. Removing the instance ends its lifecycle; there is no separate TPO subscription to close.
Session alignment
The built-in profile uses fixed UTC-anchored session windows. Bar timestamps must remain absolute Unix milliseconds; do not shift or relabel timestamps to force a local exchange open onto a UTC boundary, because that corrupts chart time and cross-series alignment. If the product needs a non-UTC anchor, holidays, or a split schedule, implement a custom profile indicator with the exchange calendar while preserving the bars' absolute timestamps. See Timezones and Sessions.
Validate the result
For a known session, confirm that each bar's high-to-low range stamps the expected letter rows. The displayed POC, 70% value area, initial balance, single prints, and naked-POC status should reconcile to those same bars.
Troubleshooting
- The TPO row is missing: confirm
features.orderFlowis enabled and the customer's built-in-indicator catalog permitsTS_MARKET_PROFILE. No depth or tape capability enables TPO. - The row is enabled but no profile appears: confirm the chart has at least two ascending OHLC bars and the typical bar interval is shorter than
sessionMinutes. - A configured period looks coarser than requested: the effective letter period is the larger of
blockMinutesand the source bar interval. A 60-minute bar cannot truthfully produce 15-minute letters. - The silhouette appears but letters do not: letter text is hidden at compact zoom or row heights while the count-bar silhouette remains. Zooming changes detail only; it does not change the profile calculation.
- A session starts left of the viewport: its silhouette remains visible while its true profile width still intersects the pane. Letter text returns only when the complete letter grid fits; a fully offscreen profile is culled.
- Auto rows differ from the visible candles: this is expected. Auto row size uses the loaded UTC-session median, not the viewport. Set a positive
rowSizefor an exact market-specific ladder. - Session boundaries are wrong: verify absolute Unix-millisecond bar timestamps and UTC anchoring. Do not repair a non-UTC exchange calendar by shifting timestamps; use a custom calendar-aware indicator.
Depth and tape flags cannot fix missing or invalid OHLC history.
If addBuiltInIndicator rejects, treat that as an entitlement, catalog, or
input-contract failure. Do not add a second custom profile as a fallback for a
customer whose catalog does not permit TS_MARKET_PROFILE.
Next steps
- Built-in Indicators — add and manage catalog indicators.
- Footprint Charts — integrate executed buy/sell volume by price row.
- Historical Bars — verify timestamps, sorting, pagination, and symbol identity.