TradingView Advanced Charts v33: Session Volume Profile, A Datafeed Skill For Coding Assistants And Three Breaking Changes, In Code
On 13 October 2026 TradingView released version 33.0.0 of Advanced Charts and Trading Platform. It adds Session Volume Profile as a chart type and an indicator, lets custom indicators sit under the price like the built-in Volume, ships an agent skill that teaches Claude Code, Codex, Cursor or Copilot the datafeed rules, and breaks three things: string shortcuts, custom fields on orders and positions, and the grid line override. This guide builds the datafeed that serves finer bars to the profile, a server-side point of control and value area check, a codemod for the breaking changes and a CI step that runs TradingView's own datafeed checker.
AlchmAI Engineering24 min read
13 Oct 2026
Release date of TradingView Advanced Charts and Trading Platform version 33.0.0, per the library release notes
~40 fewer
Files the default v33 chart fetches at startup, and about 25 fewer for a bare chart with every toolbar disabled
200,000
Maximum bars that volume_profile.history_sessions may add to the minimum countBack of each affected series
1.25m
npm downloads of Lightweight Charts in the week of 6 to 12 October, with no release since 5.2.1 on 12 August
On 13 October 2026 TradingView published version 33.0.0 of TradingView Advanced Charts and Trading Platform, the embeddable chart that most brokers, prop firms and trading apps license rather than build. The release notes summarise it in one sentence: 'This major release adds Session Volume Profile as a chart type and an indicator, lets custom indicators sit under the price action the way the built-in Volume does, and ships a datafeed skill your coding assistant can load.' The walkthrough adds the warning that matters to anyone running the library in production: 'Three breaking changes need your attention: shortcuts, custom fields on trading entities, and grid line visibility.' For a broker CTO, TradingView Advanced Charts v33 is therefore two jobs at once: a feature your traders will ask for, and an upgrade that will not compile until the adapter code changes.
Session Volume Profile is Trading Platform only, and it is the first built-in study in the library that routinely asks your datafeed for data finer than the chart is showing. That is a bigger deal than it sounds. A five-minute chart with the profile on it may now request one-minute bars for forty sessions, through the same getBars method, and your server either serves them or the profile is built from whatever the chart already had. The new optional datafeed method getVolumeProfileResolutionForPeriod is how you keep control of that request, and the new volume_profile widget option is how you bound its memory. We build all three below in TypeScript, then a server-side calculation of the point of control and value area that cross-checks the chart's numbers in tests and feeds a descriptive AI summary.
The upgrade side is mechanical but unforgiving: printable characters in onShortcut now throw, the grid line override is gone, and every custom field on an order or position moves into an extra object. Section 3 is a codemod for all three, and section 4 puts TradingView's own datafeed checker into CI so the next upgrade is a pull request, not a weekend.
1. How Do You Feed Session Volume Profile From A TradingView Advanced Charts Datafeed?
Three documented facts shape the datafeed. First, to build an accurate profile the indicator 'can request an additional series at a resolution finer than the chart', and 'the request goes through the same getBars method as any other series, so your datafeed should either serve that resolution or reject it through onErrorCallback'. Second, if you implement getVolumeProfileResolutionForPeriod and return something finer than the chart resolution, your answer wins; otherwise the indicator picks from the resolutions you advertise in supported_resolutions, and if nothing finer is advertised it uses the chart's own bars. Third, the from and to arguments of the new method are Unix timestamps in milliseconds, while PeriodParams.from and PeriodParams.to in getBars are still seconds. Bar.time and nextTime are milliseconds. Get one of those wrong and the chart requests empty ranges forever, which is exactly the bug v33 fixed for nextTime.
The policy below is deliberately boring: one-minute bars for a range of a few sessions, five-minute bars for a day chart's forty sessions, and never a resolution the server does not hold natively, because rebuilding bars from ticks on demand is where latency complaints come from. The widget options turn on studies_extend_time_scale, which history_sessions requires, and set max_total_rows explicitly, because that is the number you will tune when a prop firm asks for 120 sessions on screen.
// TradingView Trading Platform v33.0.0. Types come from the library's charting_library.d.ts.
import type {
Bar,
ChartingLibraryWidgetOptions,
DatafeedConfiguration,
DatafeedErrorCallback,
HistoryCallback,
IBasicDataFeed,
LibrarySymbolInfo,
OnReadyCallback,
PeriodParams,
ResolveCallback,
ResolutionString,
SearchSymbolsCallback,
SubscribeBarsCallback,
} from "../../vendor/charting_library";
/** Resolutions the bar store serves natively, finest first. Nothing is rebuilt on demand. */
const SERVED_MINUTES: ReadonlyArray<[ResolutionString, number]> = [
["1" as ResolutionString, 1],
["5" as ResolutionString, 5],
["15" as ResolutionString, 15],
["60" as ResolutionString, 60],
["240" as ResolutionString, 240],
["1D" as ResolutionString, 1440],
];
const SERVED = SERVED_MINUTES.map(([r]) => r);
/** The most detail bars we are prepared to ship for one profile request. Tune per client book. */
const DETAIL_BAR_BUDGET = 60_000;
const minutesOf = (res: ResolutionString): number =>
SERVED_MINUTES.find(([r]) => r === res)?.[1] ?? Number.POSITIVE_INFINITY;
interface LiveBars {
subscribe(symbol: string, resolution: ResolutionString, onBar: (bar: Bar) => void): () => void;
}
interface BarsResponse {
bars: Bar[]; // time in milliseconds, ascending
nextTime: number | null; // milliseconds, or null when history is exhausted
}
export class SvpDatafeed implements IBasicDataFeed {
private readonly unsubscribers = new Map<string, () => void>();
constructor(private readonly baseUrl: string, private readonly live: LiveBars) {}
onReady(callback: OnReadyCallback): void {
const config: DatafeedConfiguration = {
supported_resolutions: SERVED,
supports_marks: false,
supports_timescale_marks: false,
supports_time: true,
};
setTimeout(() => callback(config), 0);
}
searchSymbols(userInput: string, exchange: string, symbolType: string, onResult: SearchSymbolsCallback): void {
const q = new URLSearchParams({ query: userInput, exchange, type: symbolType });
fetch(this.baseUrl + "/symbols/search?" + q.toString())
.then((r) => r.json())
.then(onResult)
.catch(() => onResult([]));
}
resolveSymbol(symbolName: string, onResolve: ResolveCallback, onError: DatafeedErrorCallback): void {
fetch(this.baseUrl + "/symbols/" + encodeURIComponent(symbolName))
.then((r) => (r.ok ? r.json() : Promise.reject(new Error("unknown symbol"))))
.then((info: LibrarySymbolInfo) => {
// Advertise only what we serve. If nothing finer is advertised, SVP uses the chart's own bars.
onResolve({ ...info, supported_resolutions: SERVED, has_intraday: true, has_daily: true });
})
.catch((e: Error) => onError(e.message));
}
/**
* v33: the library asks which resolution to build a volume profile from.
* from and to are milliseconds here; PeriodParams in getBars are seconds.
*/
getVolumeProfileResolutionForPeriod(
currentResolution: ResolutionString,
from: number,
to: number,
_symbolInfo: LibrarySymbolInfo,
): ResolutionString {
const spanMinutes = Math.max(0, to - from) / 60_000;
const chartMinutes = minutesOf(currentResolution);
for (const [res, mins] of SERVED_MINUTES) {
if (mins >= chartMinutes) break; // must be finer than the chart
if (spanMinutes / mins <= DETAIL_BAR_BUDGET) return res;
}
return currentResolution; // no finer series within budget: use the chart's bars
}
getBars(
symbolInfo: LibrarySymbolInfo,
resolution: ResolutionString,
period: PeriodParams,
onResult: HistoryCallback,
onError: DatafeedErrorCallback,
): void {
if (!SERVED.includes(resolution)) {
onError("resolution " + resolution + " is not served"); // documented alternative to serving it
return;
}
const q = new URLSearchParams({
symbol: symbolInfo.ticker ?? symbolInfo.name,
resolution,
from: String(period.from), // seconds, as PeriodParams defines them
to: String(period.to),
countBack: String(period.countBack),
});
fetch(this.baseUrl + "/bars?" + q.toString())
.then((r) => (r.ok ? r.json() : Promise.reject(new Error("bars " + r.status))))
.then((res: BarsResponse) => {
if (res.bars.length === 0) {
onResult([], { noData: true, nextTime: res.nextTime ?? undefined }); // nextTime in ms (v33)
return;
}
onResult(res.bars, { noData: false });
})
.catch((e: Error) => onError(e.message));
}
subscribeBars(symbolInfo: LibrarySymbolInfo, resolution: ResolutionString, onTick: SubscribeBarsCallback, listenerGuid: string): void {
const stop = this.live.subscribe(symbolInfo.ticker ?? symbolInfo.name, resolution, onTick);
this.unsubscribers.set(listenerGuid, stop);
}
unsubscribeBars(listenerGuid: string): void {
this.unsubscribers.get(listenerGuid)?.();
this.unsubscribers.delete(listenerGuid);
}
}
export function widgetOptions(container: HTMLElement, datafeed: IBasicDataFeed, symbol: string): ChartingLibraryWidgetOptions {
return {
container,
datafeed,
symbol,
interval: "5" as ResolutionString,
library_path: "/charting_library/",
locale: "en",
enabled_features: ["studies_extend_time_scale"], // required by history_sessions
volume_profile: { max_total_rows: 6000, history_sessions: 40 }, // 40 sessions prefetched, 500 is the cap
overrides: {
"paneProperties.vertGridProperties.visible": false, // v33 replaces paneProperties.gridLinesMode
"paneProperties.horzGridProperties.visible": true,
},
};
}- history_sessions is a prefetch floor, not a limit: it extends the shared time scale for every source on the chart, is clamped at 500, and contributes at most 200,000 bars to the minimum countBack of each affected series. On tick resolutions the full 200,000 applies every time, because a session length cannot predict a tick count.
- max_total_rows applies to Volume Profile Visible Range and Fixed Range as well. Those draw one profile, so they report an error above the limit instead of dropping old profiles. Session Volume Profile drops its oldest profiles, which is what bounds how far back it draws.
- Returning currentResolution from getVolumeProfileResolutionForPeriod does not stop the indicator looking for finer data; it falls back to the advertised list. If you want the chart's own bars used, advertise nothing finer for that symbol.
2. Cross-Checking The Chart's Point Of Control And Value Area On The Server
Why compute the profile twice? Two reasons, and neither is distrust of TradingView. The first is testing: when the chart draws a point of control at one price and the risk desk's report says another, you need a third number computed from the same bars with a method you can read. The second is AI chart analysis. If a model is going to write 'the session's point of control sat at 7,412.5 with a value area from 7,398.0 to 7,431.5', those numbers must come from deterministic code, not from the model reading a screenshot. That is Signed-off analysis: code detects, a model explains only those facts in agreed wording, and the output stays descriptive, never advisory. Where volume traded is a description; what price might do next is advice, and the wording check blocks it.
The implementation follows the documented behaviour where it is documented. TradingView's profile uses a Row Size of 24 by default and a Value Area Volume of 70%, and 'a bar that spans several rows distributes its volume across them proportionally to the price overlap, so the bar's total volume is preserved instead of being assigned to a single row'. The value area expansion rule is not published, so the test compares the point of control to within one row and each value area bound to within one row of the chart's, which is the tolerance we have found holds in practice. Note that it is a tolerance, not an identity.
export interface SessionBar {
time: number; // ms
high: number;
low: number;
volume: number;
}
export interface ProfileOptions {
rows: number; // TradingView default Row Size: 24
valueAreaPercent: number; // TradingView default Value Area Volume: 70
}
export interface SessionProfile {
sessionStart: number;
sessionEnd: number;
rowHeight: number;
rows: { low: number; high: number; volume: number }[];
totalVolume: number;
pocPrice: number; // midpoint of the point-of-control row
valueAreaLow: number;
valueAreaHigh: number;
}
export const TV_DEFAULTS: ProfileOptions = { rows: 24, valueAreaPercent: 70 };
export function buildSessionProfile(bars: SessionBar[], opts: ProfileOptions = TV_DEFAULTS): SessionProfile {
if (bars.length === 0) throw new Error("a session needs at least one bar");
if (bars.some((b) => !Number.isFinite(b.volume))) throw new Error("every bar needs volume");
const sessionLow = Math.min(...bars.map((b) => b.low));
const sessionHigh = Math.max(...bars.map((b) => b.high));
const span = Math.max(sessionHigh - sessionLow, Number.EPSILON);
const rowHeight = span / opts.rows;
const rows = Array.from({ length: opts.rows }, (_, i) => ({
low: sessionLow + i * rowHeight,
high: sessionLow + (i + 1) * rowHeight,
volume: 0,
}));
// Distribute each bar's volume across the rows it overlaps, in proportion to the overlap.
for (const bar of bars) {
const barSpan = Math.max(bar.high - bar.low, Number.EPSILON);
for (const row of rows) {
const overlap = Math.min(bar.high, row.high) - Math.max(bar.low, row.low);
if (overlap > 0) row.volume += bar.volume * (overlap / barSpan);
}
}
const totalVolume = rows.reduce((s, r) => s + r.volume, 0);
let poc = 0;
rows.forEach((r, i) => { if (r.volume > rows[poc].volume) poc = i; });
// Expand from the POC, taking the heavier neighbour each step, until the target share is reached.
const target = totalVolume * (opts.valueAreaPercent / 100);
let lo = poc, hi = poc, covered = rows[poc].volume;
while (covered < target && (lo > 0 || hi < rows.length - 1)) {
const below = lo > 0 ? rows[lo - 1].volume : -1;
const above = hi < rows.length - 1 ? rows[hi + 1].volume : -1;
if (above > below) { hi += 1; covered += rows[hi].volume; }
else { lo -= 1; covered += rows[lo].volume; }
}
return {
sessionStart: bars[0].time,
sessionEnd: bars[bars.length - 1].time,
rowHeight,
rows,
totalVolume,
pocPrice: (rows[poc].low + rows[poc].high) / 2,
valueAreaLow: rows[lo].low,
valueAreaHigh: rows[hi].high,
};
}
/** Values read back from the chart's study, for the test to compare against. */
export interface ChartProfileReading { pocPrice: number; valueAreaLow: number; valueAreaHigh: number }
/** Throws with a precise message when the server and the chart disagree by more than one row. */
export function assertMatchesChart(server: SessionProfile, chart: ChartProfileReading): void {
const tol = server.rowHeight;
const checks: [string, number, number][] = [
["point of control", server.pocPrice, chart.pocPrice],
["value area low", server.valueAreaLow, chart.valueAreaLow],
["value area high", server.valueAreaHigh, chart.valueAreaHigh],
];
for (const [name, ours, theirs] of checks) {
if (Math.abs(ours - theirs) > tol) {
throw new Error(name + " differs by more than one row: server " + ours.toFixed(4) + ", chart " + theirs.toFixed(4));
}
}
}
/**
* The only facts an AI summary may state about the profile. Descriptive, past tense, no inference.
* The language model is handed this object, not the chart, and the wording check runs on its output.
*/
export function describeProfile(p: SessionProfile, symbol: string, decimals: number, opts: ProfileOptions = TV_DEFAULTS) {
const f = (n: number) => n.toFixed(decimals);
const date = new Date(p.sessionStart).toISOString().slice(0, 10);
return {
symbol,
session: date,
facts: [
"In the " + date + " session, " + symbol + " traded the most volume at " + f(p.pocPrice) + " (point of control).",
opts.valueAreaPercent + "% of the session's volume traded between " + f(p.valueAreaLow) + " and " + f(p.valueAreaHigh) + " (value area).",
"The profile used " + opts.rows + " rows of " + f(p.rowHeight) + " each.",
],
};
}- Run the comparison in CI against a handful of recorded sessions per asset class, reading the chart's values through a headless browser. When the tolerance fails, it is usually the session boundary: an FX session that rolls at 5pm New York, or a CFD on an index whose pre-market bars you include and the chart's 'Market only' setting excludes.
- The bars you feed this function must be the same bars the chart received at the detailed resolution, not the chart resolution. A one-minute profile and a five-minute profile of the same session can legitimately differ by more than a row.
- Keep describeProfile's output as the only path into the model. If the prompt contains the chart image or the raw bars, you have lost the guarantee that every number in the summary traces to code.
3. What Breaks When You Upgrade To v33, And The Codemod That Fixes It
The release notes list three breaking changes, and each one lands in a different part of a broker's integration. Shortcuts: string key names were deprecated in version 30 and are now removed, so passing a printable character to onShortcut, whether as the string ctrl+shift+/ or as the array form with '/' as its last element, now throws an error; modifiers (ctrl, shift, alt, and meta, cmd or command) stay as strings and the key becomes a numeric key code, 191 for the forward slash in the documented example. Grid lines: paneProperties.gridLinesMode is replaced by paneProperties.vertGridProperties.visible and paneProperties.horzGridProperties.visible, both defaulting to true, with saved layouts migrated automatically; grid lines are also 'dotted and less transparent by default, including on existing layouts', so set style and color on the same properties if your brand guide cares. Trading entities: CustomFields is replaced by WithExtraData, so PlacedOrder, Position, IndividualPosition and Execution 'no longer accept your own properties next to the library's own'; they move into extra, and an Account Manager column's dataFields becomes a path, ['extra', 'balance'] instead of 'balance'.
Two more changes are not breaking but belong in the same pull request. nextTime is now read in milliseconds, matching Bar.time; a value in seconds still works and logs a console warning, and UDF integrations are unaffected because the adapter converts. And the new setShortcutsEnabled(enabled, { keepCustom: true }) method disables the built-in shortcuts and typing-to-change-symbol while keeping yours, with areShortcutsEnabled() to read the state, which replaces hand-written override code on keyboard-driven terminals. The codemod below is a dry-run-by-default script over your source tree. The custom-field change cannot be done safely by text replacement, because only the compiler knows which properties are yours, so the script ships a runtime adapter for the broker boundary and lets tsc find every remaining read.
// Dry run: npx tsx scripts/migrate-tv-v33.ts src
// Write: npx tsx scripts/migrate-tv-v33.ts src --write
import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
/** Legacy KeyboardEvent.keyCode values for printable keys on a US layout. */
const PUNCTUATION: Record<string, number> = {
";": 186, "=": 187, ",": 188, "-": 189, ".": 190, "/": 191, "`": 192,
"[": 219, "\\": 220, "]": 221, "'": 222, " ": 32,
};
const MODIFIERS = new Set(["ctrl", "shift", "alt", "meta", "cmd", "command"]);
export function keyCodeFor(key: string): number {
if (/^[a-z]$/i.test(key)) return key.toUpperCase().charCodeAt(0); // A..Z = 65..90
if (/^[0-9]$/.test(key)) return key.charCodeAt(0); // 0..9 = 48..57
const code = PUNCTUATION[key];
if (code === undefined) throw new Error("no key code for '" + key + "'; map it by hand");
return code;
}
/** 'ctrl+shift+/' or ['ctrl', 'shift', '/'] -> ['ctrl', 'shift', 191] */
export function migrateShortcuts(src: string): string {
const asArray = (parts: string[]) =>
"[" + parts.map((p) => (MODIFIERS.has(p) ? "'" + p + "'" : String(keyCodeFor(p)))).join(", ") + "]";
return src
.replace(/onShortcut\(\s*(['"])([^'"]+)\1/g, (_m, _q, combo: string) =>
"onShortcut(" + asArray(combo.split("+")))
.replace(/onShortcut\(\s*\[([^\]]+)\]/g, (m, inner: string) => {
const parts = inner.split(",").map((p) => p.trim());
if (parts.every((p) => /^\d+$/.test(p) || MODIFIERS.has(p.replace(/['"]/g, "")))) return m; // already numeric
return "onShortcut(" + asArray(parts.map((p) => p.replace(/^['"]|['"]$/g, "")));
});
}
/** 'paneProperties.gridLinesMode': 'horz' -> the two v33 visibility flags. */
export function migrateGridLines(src: string): string {
return src.replace(
/(['"])paneProperties\.gridLinesMode\1\s*:\s*(['"])(\w+)\2/g,
(_m, _q1, _q2, mode: string) => {
const m = mode.toLowerCase();
const vert = m === "both" || m.includes("vert");
const horz = m === "both" || m.includes("horz");
return "'paneProperties.vertGridProperties.visible': " + vert +
",\n 'paneProperties.horzGridProperties.visible': " + horz;
});
}
/**
* CustomFields -> WithExtraData, applied at the broker boundary rather than by regex.
* List the fields that are yours; everything else is the library's and stays put.
*/
export function withExtra<T extends Record<string, unknown>, K extends keyof T & string>(
entity: T,
customKeys: readonly K[],
): Omit<T, K> & { extra: Pick<T, K> } {
const extra: Record<string, unknown> = {};
const rest: Record<string, unknown> = {};
for (const [k, v] of Object.entries(entity)) {
if ((customKeys as readonly string[]).includes(k)) extra[k] = v;
else rest[k] = v;
}
return { ...(rest as Omit<T, K>), extra: extra as Pick<T, K> };
}
/** dataFields: ['balance'] -> dataFields: [['extra', 'balance']] for the keys you declare custom. */
export function migrateDataFields(src: string, customKeys: readonly string[]): string {
return src.replace(/dataFields\s*:\s*\[([^\]]*)\]/g, (m, inner: string) => {
const fields = inner.split(",").map((f) => f.trim()).filter(Boolean);
if (fields.length === 0 || fields.some((f) => f.startsWith("["))) return m; // already paths
const out = fields.map((f) => {
const name = f.replace(/^['"]|['"]$/g, "");
return customKeys.includes(name) ? "['extra', '" + name + "']" : f;
});
return "dataFields: [" + out.join(", ") + "]";
});
}
function walk(dir: string, out: string[] = []): string[] {
for (const name of readdirSync(dir)) {
const p = join(dir, name);
if (name === "node_modules" || name.startsWith(".")) continue;
if (statSync(p).isDirectory()) walk(p, out);
else if (/\.(ts|tsx|js|jsx)$/.test(name)) out.push(p);
}
return out;
}
if (require.main === module) {
const [root = "src", flag] = process.argv.slice(2);
const customKeys = (process.env.TV_CUSTOM_FIELDS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
let changed = 0;
for (const file of walk(root)) {
const before = readFileSync(file, "utf8");
const after = migrateDataFields(migrateGridLines(migrateShortcuts(before)), customKeys);
if (after === before) continue;
changed += 1;
console.log((flag === "--write" ? "rewrote " : "would rewrite ") + file);
if (flag === "--write") writeFileSync(file, after);
}
console.log(changed + " file(s) " + (flag === "--write" ? "changed" : "need changes") + ". Now run tsc: excess-property errors mark the custom fields still to move into extra.");
}4. What Does The Datafeed Agent Skill Do For A Team Using Coding Assistants?
The second headline feature is aimed at how code gets written now. The library repository ships an Agent Skill, a folder of instructions and scripts that an assistant such as Claude Code, Codex, Cursor or GitHub Copilot loads when a task needs it, and the documentation states the purpose plainly: 'The skill gives the assistant the library's own rules instead of guesses from training data.' The charting-library-datafeed skill covers the Datafeed API, the UDF adapter, symbology, trading sessions, resolutions, real-time updates and reconnects, quotes for Trading Platform, and the common datafeed issues, plus saving and loading and customisation precedence. It instructs the assistant to answer a datafeed question in one of three ways: name the existing feature that does it, state plainly that the library does not support it and give a workaround, or find the rule the implementation breaks, fix it and prove the fix with the checker.
That checker is the part a CTO should care about, because it works without any assistant at all. scripts/check-datafeed.mjs 'calls your datafeed the same way the library does and reports every violation it finds'; each finding names the rule, the symptom on the chart and the documentation page. It needs Node.js 18 or later and has no dependencies. It runs against a datafeed module (--datafeed ./src/datafeed.js --symbol AAPL) or a UDF server (--udf https://your-server.example.com --symbol AAPL), and for a browser-only datafeed there is createCheckedDatafeed(datafeed), a wrapper that changes nothing and logs violations to the console. Two caveats. The skill lives in the library repository, which is access-restricted, so npx skills add tradingview/charting_library --skill charting-library-datafeed (or tradingview/trading_platform) uses your Git access, and installing the library from npm does not include it. And the skill matches the library version in the repository, so update both together. For CI, vendor the skill folder next to the library build you ship and run the checker against the staging UDF endpoint on every pull request.
name: TradingView datafeed check
on:
pull_request:
paths:
- "src/datafeed/**"
- "udf-server/**"
- ".agents/skills/charting-library-datafeed/**"
jobs:
check-datafeed:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
# Staging UDF endpoint; the secret holds only the URL, never a key.
UDF_URL: ${{ secrets.STAGING_UDF_URL }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20" # the checker needs Node.js 18 or later and has no dependencies
- name: Confirm the vendored skill matches the shipped library version
run: |
test -f .agents/skills/charting-library-datafeed/scripts/check-datafeed.mjs
node -e "const v=require('./vendor/charting_library/package.json').version; console.log('library', v)"
- name: Run TradingView's datafeed checker against staging
run: |
for SYMBOL in "EURUSD" "UK100" "AAPL"; do
node .agents/skills/charting-library-datafeed/scripts/check-datafeed.mjs \
--udf "$UDF_URL" --symbol "$SYMBOL"
done
- name: Migration dry run must be clean on main
run: npx tsx scripts/migrate-tv-v33.ts src | tee migrate.log && ! grep -q "would rewrite" migrate.logTwo smaller items from the release are worth a line each, because they change a broker's defaults. The always_require_order_confirmation featureset, Trading Platform only and off by default, turns off one-click order placement outright: the One-click trading setting 'is hidden and cannot be turned on, including through saved settings or silentOrdersPlacement'. That is a one-line answer to the compliance question of whether a client can bypass order confirmation by accident, and we would turn it on for any retail CFD book. And custom indicators can now declare overlayScaleMargins in their metainfo, for example { top: 0.75, bottom: 0 }, to sit in the bottom quarter of the series pane without a visible price scale, as the built-in Volume does; it is ignored in the indicator's own pane, and values that are negative or sum to 1 or more are rejected with a console warning.
What Else Moved In Trading Charts This Fortnight?
Lightweight Charts, TradingView's open-source sibling, had no npm release in the window: 5.2.1 from 12 August 2026 remains current, and it was downloaded about 1.25 million times from npm in the week of 6 to 12 October. The repository was busy regardless. On 9 October pull request #2159 merged a scatter series plugin, @tradingview/lwc-plugin-scatter-series, with helpers in lwc-toolkit 1.1.0; neither the plugin nor that toolkit version is on npm yet. On 5 October issue #2146 closed, after a maintainer confirmed that 'the production property-rename step mangles the scheduler.postTask probe in 5.1.0 and later', so precomputeConflationOnInit never used the scheduler and precomputeConflationPriority had no effect. The fix is on master and the bug is live in 5.2.1, which matters if you tuned conflation on a dashboard and saw no difference.
SciChart.js version 6 reached npm as 6.0.1 on 24 September and 6.0.8 on 13 October. Its README describes a new WebGPU renderer 'selected automatically on Apple Silicon Macs, with WebGL used everywhere else'. And demand for AI-native charting is visible in public: a Show HN post on 6 October, 'OpenChart, OSS TradingView alternative with your own AI agent', reached 45 points and 20 comments, while TradingView's own charting-library-examples repository stands at 1,920 stars. Our reading: the charting market is splitting into a rendering layer, where WebGPU and conflation are the fight, and an agent layer, where the question is which facts an agent may state. The datafeed skill is TradingView's bet on the second.
“A volume profile is only as honest as the volume under it. Before a broker draws one, it should be able to say whose volume it is.”
Where This Belongs
- 01Brokers and CFD providers: decide what volume means on your platform before you enable Session Volume Profile. For a CFD on an index, the only volume you hold is your own clients' flow, not the exchange's, and a profile built from it describes your book, not the market. Either source licensed exchange volume for the underlying, or label the profile as platform volume. Then run the codemod, turn on always_require_order_confirmation for retail, and put the datafeed checker in CI.
- 02Prop trading firms: the profile is a natural companion to the evaluation dashboard, because a rule breach is easier to explain against where the volume traded. Set history_sessions to the evaluation window and raise max_total_rows gradually, measuring memory as the documentation advises.
- 03Fintech and trading startups: on the npm package of Lightweight Charts, nothing changed this fortnight except the conflation bug. If you are evaluating a licence, the agent skill is a real reduction in integration time for a small team using coding assistants, provided a person reads the checker's findings rather than accepting the assistant's fix unseen.
- 04Trading education and tools: Session Volume Profile is a good teaching object precisely because it is descriptive. Use describeProfile to generate lesson text about where volume traded in a historical session, with the wording check in front of anything a student reads.
- 05Research and quant teams: use the server-side profile for research and the chart as a visual check. The detailed-resolution request is also a useful probe of your bar store: if forty sessions of one-minute bars takes more than a second to serve, the profile will show you before a client does.
The Bottom Line
TradingView Advanced Charts and Trading Platform version 33.0.0, released on 13 October 2026, gives brokers a Session Volume Profile that asks the datafeed for finer bars, a datafeed skill that teaches coding assistants the library's rules and ships a checker you can run without one, and three breaking changes that a mechanical migration can clear in an afternoon: numeric key codes in onShortcut, per-axis grid visibility flags, and custom fields moved into extra under WithExtraData. The build in this guide is a getVolumeProfileResolutionForPeriod policy that serves what the server actually holds, a server-side point of control and value area that cross-checks the chart and feeds a descriptive AI summary, a codemod with a runtime adapter for the custom-field change, and a CI step that runs TradingView's checker against staging. The harder question is not in the release notes: whether the volume under the profile is volume a broker can stand behind. That is the kind of TradingView integration and charting work AlchmAI, a London trading technology and AI studio, does for brokers, prop firms and trading apps, and it starts with the data, not the indicator.
References & Further Reading
- TradingView - Advanced Charts release notes, Version 33.0.0 (Tue Oct 13 2026). tradingview.com/charting-library-docs/latest/releases/release-notes
- TradingView - Release 33.0.0 walkthrough: Session Volume Profile, datafeed skill and breaking changes. tradingview.com/charting-library-docs/blog/version-3300
- TradingView - Session Volume Profile (Trading Platform): inputs, datafeed requirements, history and performance. tradingview.com/charting-library-docs/latest/trading_terminal/session-volume-profile.md
- TradingView - Datafeed API additional methods: getVolumeProfileResolutionForPeriod. tradingview.com/charting-library-docs/latest/connecting_data/datafeed-api/additional-methods.md
- TradingView - Datafeed API: time units (milliseconds for Bar.time, nextTime and the volume profile range; seconds for PeriodParams). tradingview.com/charting-library-docs/latest/connecting_data/datafeed-api.md
- TradingView - IDatafeedChartApi reference (getBars, getVolumeProfileResolutionForPeriod). tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.IDatafeedChartApi
- TradingView - Agent skills: the charting-library-datafeed skill and check-datafeed.mjs. tradingview.com/charting-library-docs/latest/ai/agent-skills.md
- TradingView - Custom indicator metainfo: overlayScaleMargins. tradingview.com/charting-library-docs/latest/custom_studies/metainfo.md
- npm - lightweight-charts (5.2.1, published 12 August 2026). npmjs.com/package/lightweight-charts
- npm - lightweight-charts download count, 6 to 12 October 2026. api.npmjs.org/downloads/point/2026-10-06:2026-10-12/lightweight-charts
- GitHub - lightweight-charts pull request #2159, Add scatter series plugin and lwc-toolkit 1.1.0 helpers. github.com/tradingview/lightweight-charts/pull/2159
- GitHub - lightweight-charts issue #2146, precomputeConflationOnInit never uses scheduler.postTask. github.com/tradingview/lightweight-charts/issues/2146
- npm - scichart (SciChart.js v6 README: WebGPU renderer, 6.0.1 and 6.0.8). npmjs.com/package/scichart
- Hacker News - Show HN: OpenChart, OSS TradingView alternative with your own AI agent. news.ycombinator.com/item?id=49979793
- GitHub - tradingview/charting-library-examples. github.com/tradingview/charting-library-examples
- AlchmAI - Trading charts and AI chart analysis. alchmai.com/charting
- AlchmAI - Brokers and CFD providers. alchmai.com/solutions/broker-trading-platforms
AlchmAI Engineering
Engineering, London
Written by the AlchmAI engineering team in Mayfair, London. We build trading platforms, real-time charts, market data pipelines and AI features for brokers, prop firms and fintech teams. The Playbook is where we explain how we approach these systems, with code you can run and sources you can check.
Code in this guide is illustrative and supplied without warranty. Review and test it before production use. Nothing here is investment advice. Important information