Skip to content

feat(trading-signals-docs): add visual strategy builder - #1191

Open
bennycode wants to merge 3 commits into
feat/strategy-graphfrom
feat/graph-builder-ui
Open

feat(trading-signals-docs): add visual strategy builder#1191
bennycode wants to merge 3 commits into
feat/strategy-graphfrom
feat/graph-builder-ui

Conversation

@bennycode

Copy link
Copy Markdown
Owner

Stacked on #1190 (which stacks on #1189) — merge those first; GitHub retargets automatically.

What

A visual, node-based strategy builder at /graph-builder — n8n/Scratch-style: drag building blocks, connect typed ports, configure them inline, and backtest. The canvas state is the graph JSON that GraphStrategy executes, so what users see is exactly what runs.

Features

  • Palette from the engine's node registry — nodes added via registerNodeType() appear automatically, no UI changes
  • Typed ports (Scratch guarantee): mismatched kinds refuse to snap; occupied inputs reject a second writer; edge colors encode the value kind (candle/number/trigger)
  • Config forms inside nodes, validated by the engine's Zod schemas
  • Live validation by constructing the real interpreter on every change — the error shown is the error a backtest would throw, node-addressable
  • SMA crossover template, clear canvas, per-node delete (✕ + Backspace), JSON share/import
  • Backtesting via the existing BacktestExecutor with a buy-and-hold baseline, rendered by the existing BacktestResults

Test plan

6 Playwright specs (class-based page object per repo e2e conventions), including the flagship: build the SMA crossover from scratch — palette clicks, config edits, 10 real mouse-dragged connections — and assert the backtest matches the template run exactly (trade count + ROI). Plus: template validity, results rendering, incomplete-graph rejection, type-mismatch refusal, node deletion. Full e2e suite 9/9 green; next build prerenders the page statically.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new /graph-builder page in trading-signals-docs that provides a visual, React Flow–based strategy builder backed by the trading-strategies graph runtime, including JSON import/export and backtest execution.

Changes:

  • Introduces graph<->canvas conversion utilities and a new visual graph editor (palette, typed ports, inline node config, delete behavior).
  • Adds a new docs page that validates graphs by instantiating GraphStrategy and runs backtests (plus buy-and-hold baseline) with existing result rendering.
  • Adds Playwright E2E coverage for the builder flows and wires the builder entry into the docs navigation; adds @xyflow/react dependency + styles.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/trading-signals-docs/utils/graphIO.ts Converts React Flow nodes/edges to/from StrategyGraphInput JSON.
packages/trading-signals-docs/pages/graph-builder.tsx Implements the Strategy Builder page: validation, JSON share/import, backtest execution.
packages/trading-signals-docs/pages/_app.tsx Adds React Flow global CSS and a “Builder” nav item.
packages/trading-signals-docs/package.json Adds @xyflow/react dependency.
packages/trading-signals-docs/e2e/pages/GraphBuilderPage.ts Adds Playwright page object for graph builder interactions.
packages/trading-signals-docs/e2e/graph-builder.spec.ts Adds E2E specs for template, build-from-scratch, validation, deletion, type mismatch.
packages/trading-signals-docs/components/graph/nodeFields.ts Defines per-node editable config field descriptors for inline forms.
packages/trading-signals-docs/components/graph/GraphEditor.tsx Implements the React Flow editor: palette, connection validation, node creation, change propagation.
packages/trading-signals-docs/components/graph/BuilderNodeView.tsx Renders node UI: ports, labels, inline config widgets, delete button.
package-lock.json Updates lockfile for the new dependency and its transitive packages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +122 to +135
<input
data-testid={`node-${id}-${field.key}`}
className={common}
type={field.widget === 'number' ? 'number' : 'text'}
placeholder={field.placeholder}
value={value === undefined ? '' : String(value)}
onChange={event =>
onConfigChange(
id,
field.key,
field.widget === 'number' ? Number(event.target.value) : event.target.value
)
}
/>
Comment on lines +78 to +83
const loadGraph = useCallback((graph: StrategyGraphInput) => {
setLoadedGraph(graph);
setEditorKey(key => key + 1);
setResult(null);
setBaselineResult(null);
}, []);
Comment thread packages/trading-signals-docs/pages/graph-builder.tsx Outdated
A node-based editor (React Flow) where users stack building blocks —
candles, batchers, indicators, if-conditions, order advice — into trading
strategies, n8n/Scratch style. The canvas state is the same graph JSON
that GraphStrategy executes, so what users see is exactly what runs.

- Palette driven by the engine's node registry (registered custom nodes
  appear automatically); config forms rendered inside nodes
- Typed ports: mismatched connections refuse to snap; occupied inputs
  reject second writers
- Live validation by constructing the real interpreter on every change,
  surfacing node-addressable errors before running
- SMA crossover template, clear canvas, per-node delete, JSON share/import
- Backtests run against the existing BacktestExecutor with a buy-and-hold
  baseline, reusing BacktestResults
- Playwright coverage incl. building the full strategy from scratch via
  palette clicks and port drags, asserting results identical to the template
@bennycode
bennycode force-pushed the feat/graph-builder-ui branch from 4bb8e29 to e1593a8 Compare July 13, 2026 14:43
…ilder

- Clearing a number config field now unsets the key so the node's schema
  default applies, instead of coercing the empty string to 0
- loadGraph() syncs the current graph immediately, so validation and the
  run button never lag behind Clear/Template actions while the
  client-only canvas re-hydrates
- Clear the pending 'Copied!' reset timer on unmount
@bennycode
bennycode force-pushed the feat/graph-builder-ui branch from e1593a8 to 0d7dd5b Compare July 13, 2026 14:44
@bennycode

Copy link
Copy Markdown
Owner Author

Addressed all three review findings in 0d7dd5b:

  • Number fields: clearing the input now unsets the config key so the node's Zod schema default applies, instead of coercing '' to 0
  • loadGraph() syncs currentGraph immediately, so validation and the run button no longer lag behind Clear/Template while the client-only canvas re-hydrates
  • Copy timer is cleared on unmount

Also rebased onto the updated #1190 (deterministic topo sort). Full e2e suite re-run: green.

…e and forms

- Replace the key-remount reset (Clear/Template/JSON import) with a plain
  resetSignal prop the editor applies via setNodes/setEdges, so the canvas
  never unmounts and keeps its viewport. A forwardRef+useImperativeHandle
  version was tried first but next/dynamic hijacks the ref for its own
  {retry} handle and never forwards it to the loaded component.
- Debounce the interpreter-rebuilding validation check so it doesn't
  reconstruct GraphStrategy on every keystroke in a config field.
- Show each node's actual Zod schema default (via configSchema.safeParse({}))
  as the config field's placeholder/selected value instead of a UI guess,
  so an unset field no longer looks unrelated to what a backtest will use.
- Fix addNode: move id/position computation out of the setNodes updater so
  the updater stays pure, and replace the fixed-height grid (which let tall
  nodes like `advice` overlap the row below) with column shelf-packing based
  on each node type's estimated height.
- Add a confirm() guard before Clear canvas, and surface clipboard write
  failures in copyJson() instead of leaving them as unhandled rejections.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Node creation in GraphEditor can produce duplicate ids and incorrect placement due to using render-time nodes state rather than the latest state inside the setNodes update.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 10/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +176 to +195
const addNode = useCallback(
(nodeType: string) => {
const shortName = nodeType.replace('source:', '');
let suffix = nodes.length + 1;
let id = `${shortName}-${suffix}`;
while (nodes.some(node => node.id === id)) {
suffix += 1;
id = `${shortName}-${suffix}`;
}
// Shelf-pack into the shortest column so a tall node never overlaps the one placed after it.
const heights = columnHeights.current;
const column = heights.indexOf(Math.min(...heights));
const position = {x: GRID_ORIGIN_X + column * COLUMN_WIDTH, y: heights[column]};
heights[column] += estimateNodeHeight(nodeType) + NODE_GAP_Y;

const newNode: BuilderNode = {data: {config: {}, nodeType}, id, position, type: 'builder'};
setNodes(current => [...current, newNode]);
},
[nodes, setNodes]
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants