Skip to content

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"]
  1. Drag indicators onto the canvas — the builder exposes all 37 supported technical indicators grouped by category (trend, momentum, volatility, volume).
  2. Define variables — configure indicator parameters like period lengths, thresholds, and price sources through input panels.
  3. Build conditions — compose boolean expressions using AND/OR groups. Each condition compares two operands (indicator vs. value, indicator vs. indicator).
  4. Map to signals — assign conditions to the four signal slots: long_entry, long_exit, short_entry, short_exit.
  5. Generate PineScript — the frontend emits valid PineScript v6 source with strategy() declaration, input.*() calls, indicator computations, and if blocks with strategy.entry()/strategy.close().
  6. 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.