Visual Strategy Builder¶
Users create trading strategies visually without writing code. The strategy builder is a drag-and-drop canvas powered by Vue Flow where traders define indicators, compose signal conditions using expression trees, and wire up entry/exit logic — all through a graphical interface. Behind the scenes, the builder generates PineScript v6 source code that is compiled server-side into vectorized Python and executed by the backtesting engine. The result is a zero-code path from idea to backtest in under 60 seconds.
How It Works¶
flowchart TB
A["Drag Indicators"] --> B["Define Variables"]
B --> C["Build Conditions"]
C --> D["Map to 4 Signals"]
D --> E["Generate PineScript v6"]
E --> F["Compile to Python"]
F --> G["Run Backtest"]
G --> H["Display Results"]
- Drag indicators onto the canvas — the builder exposes all 37 supported technical indicators grouped by category (trend, momentum, volatility, volume).
- Define variables — configure indicator parameters like period lengths, thresholds, and price sources through input panels.
- Build conditions — compose boolean expressions using AND/OR groups. Each condition compares two operands (indicator vs. value, indicator vs. indicator).
- Map to signals — assign conditions to the four signal slots:
long_entry,long_exit,short_entry,short_exit. - Generate PineScript — the frontend emits valid PineScript v6 source with
strategy()declaration,input.*()calls, indicator computations, andifblocks withstrategy.entry()/strategy.close(). - Compile and backtest — the server-side PineScript compiler transforms the source into vectorized Python, and the backtesting engine runs it against historical data.
The 4-Signal Model¶
Every strategy — whether built visually, written in PineScript, or coded in raw Python — must produce exactly 4 boolean signals. This is the universal interface between strategy logic and both the execution engine and the backtester.
| Signal | Meaning | PineScript Equivalent |
|---|---|---|
long_entry |
Open a long position | strategy.entry("Long", strategy.long) |
long_exit |
Close a long position | strategy.close("Long") |
short_entry |
Open a short position | strategy.entry("Short", strategy.short) |
short_exit |
Close a short position | strategy.close("Short") |
flowchart LR
subgraph source["Strategy Source"]
VB["Visual Builder"]
PS["PineScript"]
PY["Raw Python"]
end
SIGNALS["4 Boolean Signals<br/>long_entry, long_exit<br/>short_entry, short_exit"]
subgraph consumers["Consumers"]
BT["Backtester"]
EX["Live Execution"]
end
VB --> SIGNALS
PS --> SIGNALS
PY --> SIGNALS
SIGNALS --> BT
SIGNALS --> EX
Why 4 signals?
The 4-signal model decouples strategy logic from execution mechanics. The backtester doesn't need to understand MACD crossovers or RSI thresholds — it only sees boolean arrays. This also enables the auto-reversal logic in live trading: a long_entry signal automatically closes any existing short position before opening the long.
Components¶
The strategy builder is composed of 11 Vue 3 components using the Composition API with <script setup>:
| Component | File | Purpose |
|---|---|---|
| StrategyBuilderPage | StrategyBuilderPage.vue |
Main editor — hosts the visual flow canvas, sidebar panels, and PineScript preview. Orchestrates state across all child components. |
| SignalConditionBuilder | SignalConditionBuilder.vue |
Builds signal conditions for each of the 4 signal slots. Each signal gets its own expression tree of AND/OR conditions. |
| ExpressionBuilder | ExpressionBuilder.vue |
Expression tree editor with a function picker. Users compose conditions like RSI(14) < 30 AND SMA(20) > SMA(50). |
| ExprNode | ExprNode.vue |
Renders a single node in the expression tree — handles recursive nesting of AND/OR groups. |
| VariablesPanel | VariablesPanel.vue |
Manages indicator and variable definitions. Users add indicators (SMA, MACD, BB) and assign them to named variables. |
| StrategyInputsPanel | StrategyInputsPanel.vue |
Manages configurable input parameters — period lengths, thresholds, multipliers. These become input.int() / input.float() in the generated PineScript. |
| PineScriptPreview | PineScriptPreview.vue |
Live, read-only preview of the generated PineScript v6 code. Updates in real-time as the user modifies the visual builder. |
| OperandSelector | OperandSelector.vue |
Dropdown selector for technical indicators and price sources. Groups indicators by category (Trend, Momentum, Volatility, Volume). |
| ValueDisplay | ValueDisplay.vue |
Renders operand values in condition rows — displays indicator names, numeric constants, or variable references with appropriate formatting. |
| ConditionRow | ConditionRow.vue |
A single condition: left_operand operator right_operand (e.g., RSI(14) > 70). Supports comparison operators: >, <, >=, <=, ==, !=, crossover, crossunder. |
| ConditionGroup | ConditionGroup.vue |
Groups multiple ConditionRow instances with AND/OR logic. Supports nesting for complex boolean expressions. |
Generated PineScript Example¶
The builder generates fully valid PineScript v6 source code. Here's an example of a MACD crossover strategy with an RSI filter — the kind of strategy a user would build by dragging a MACD indicator, an RSI indicator, and configuring crossover conditions:
//@version=6
strategy("MACD + RSI Filter", overlay=true)
// Input parameters (from StrategyInputsPanel)
fast_length = input.int(12, "Fast Length")
slow_length = input.int(26, "Slow Length")
signal_length = input.int(9, "Signal Length")
rsi_length = input.int(14, "RSI Length")
rsi_upper = input.int(70, "RSI Overbought")
rsi_lower = input.int(30, "RSI Oversold")
// Indicator calculations (from VariablesPanel)
[macdLine, signalLine, _] = ta.macd(close, fast_length, slow_length, signal_length)
rsi_val = ta.rsi(close, rsi_length)
// Signal conditions (from SignalConditionBuilder)
longCondition = ta.crossover(macdLine, signalLine) and rsi_val < rsi_upper
shortCondition = ta.crossunder(macdLine, signalLine) and rsi_val > rsi_lower
// Entry signals — 4-signal model
if longCondition
strategy.entry("Long", strategy.long)
if shortCondition
strategy.entry("Short", strategy.short)
What the compiler sees
The compiler maps this to 4 signals: long_entry = crossover(macd, signal) & (rsi < 70), short_entry = crossunder(macd, signal) & (rsi > 30). Since there are no explicit strategy.close() calls, long_exit and short_exit are empty — positions are reversed by opposite entries (handled by the auto-reversal logic).
Integration with Backend¶
sequenceDiagram
participant User as User (Browser)
participant Builder as Strategy Builder
participant API as FastAPI
participant DB as PostgreSQL
participant Compiler as PineScript Compiler
participant Backtester as Backtest Engine
participant Frontend as Results UI
User->>Builder: Drag indicators, build conditions
Builder->>Builder: Generate PineScript v6 (real-time)
User->>Builder: Click "Run Backtest"
Builder->>API: POST /api/strategies<br/>{pine_source, symbol, timeframe}
API->>DB: Store strategy (pine_source, params)
API->>Compiler: transform_pinescript(source)
Compiler->>Compiler: Tokenize → Parse → CodeGen
Compiler-->>API: TransformedStrategy (compute fn + metadata)
API->>Backtester: run(strategy, symbol, timeframe)
Backtester->>Backtester: Load data → Magnifier → vectorbt
Backtester-->>API: BacktestResult (stats + equity + trades)
API-->>Frontend: JSON response
Frontend->>User: Equity curve, stats cards, trade table
Data Flow Summary¶
| Step | Action | Data |
|---|---|---|
| 1 | User builds strategy | Visual flow graph |
| 2 | Frontend generates PineScript | .pine source string |
| 3 | POST /api/strategies |
{pine_source, symbol, timeframe, period} |
| 4 | Store in PostgreSQL | Strategy record with versioned source |
| 5 | Compiler transforms | PineScript → AST → Python _compute() function |
| 6 | Backtester executes | _compute(df, params) → 4 boolean Series → vbt.Portfolio.from_signals() |
| 7 | Results returned | BacktestResult with ~30 metrics, equity curve, trade list |
| 8 | Frontend renders | Stats cards, area charts, trade table |
Live Preview
The PineScriptPreview component updates in real-time as the user modifies the visual builder. This gives immediate feedback — users can verify the generated code before submitting. The preview is read-only and uses syntax highlighting via a <pre> block with PineScript-aware formatting.