Skip to main content

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

InputDefaultEffect
sessionMinutes1440Length of each UTC-anchored profile session
blockMinutes30Time represented by one TPO letter
rowSize0Price per profile row; zero selects automatic sizing
valueAreaRatio0.7Share of TPO count included around the POC
rowsPerSession30Target row count used only by automatic row sizing
initialBalanceBlocks2Opening blocks used for initial balance
profileWidthPercent90Maximum share of the session width available to the profile
showLetterstrueDraw TPO letters when the complete session text fits
showValueAreatrueDraw value-area rows and VAH/VAL guides
showPoctrueDraw the session point-of-control guide
showNakedPoctrueExtends POC until a later session trades through it
showSinglePrintsfalseHighlights internal rows touched by one letter only
showInitialBalancetrueDraw the opening-block range
showSessionRangefalseDraw each session's complete high-to-low range
showLabelsfalseDraw VAH, VAL, POC, initial-balance, and range labels when their guides are visible
opacity0.85Profile opacity from 0.1..1
fontSize9TPO 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

StyleDefaultEffect
neutralColor#64748bRows outside the value area
valueAreaColor#a78bfaValue-area rows and VAH/VAL guides
pocColor#f59e0bPOC row and guide
initialBalanceColor#38bdf8Initial-balance range
singlePrintColor#fb7185Single-print runs
sessionRangeColor#94a3b8Session high/low range
textColor#f8fafcOrdinary TPO letters and labels
pocTextColor#0f172aLetters 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.orderFlow is enabled and the customer's built-in-indicator catalog permits TS_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 blockMinutes and 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 rowSize for 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