# PineScript-to-Swym DSL Converter — Feasibility Assessment

## Context

The swym rule-based strategy DSL can express a useful subset of PineScript strategies, but writing the JSON by hand is tedious and error-prone. A converter that takes PineScript source and emits valid swym DSL JSON would lower the barrier to backtesting TradingView strategies on swym. The user already has one hand-translated example (MNQ Liquidity Sweep) and wants to know how far automated conversion could go.

## What Maps Cleanly (PineScript → swym DSL)

These PineScript constructs have direct swym equivalents:

| PineScript | Swym DSL |
|---|---|
| `open`, `high`, `low`, `close`, `volume` | `{ "kind": "field", "field": "close" }` |
| `close[1]`, `high[3]` (bar offset) | `{ "kind": "field", "field": "close", "offset": 1 }` |
| `ta.highest(high, 20)`, `ta.lowest(low, 20)` | `{ "kind": "func", "name": "highest", ... }` |
| `ta.sma(close, 50)`, `ta.ema(close, 21)` | `{ "kind": "func", "name": "sma", ... }` |
| `ta.rsi(close, 14)` | `{ "kind": "func", "name": "rsi", ... }` |
| `ta.stdev(close, 20)` | `{ "kind": "func", "name": "std_dev", ... }` |
| `ta.crossover(a, b)` | `{ "kind": "cross_over", "left": ..., "right": ... }` |
| `ta.crossunder(a, b)` | `{ "kind": "cross_under", ... }` |
| `a > b`, `a < b`, `a >= b`, `a <= b`, `a == b` | `{ "kind": "compare", "op": ">", ... }` |
| `a + b`, `a - b`, `a * b`, `a / b` | `{ "kind": "bin_op", "op": "add", ... }` |
| `cond1 and cond2` | `{ "kind": "all_of", "conditions": [...] }` |
| `cond1 or cond2` | `{ "kind": "any_of", "conditions": [...] }` |
| `not cond` | `{ "kind": "not", "condition": ... }` |
| `strategy.entry("Long", strategy.long, when=cond)` | Rule with `"state": "flat"` + buy action |
| `strategy.close("Long", when=cond)` | Rule with `"state": "long"` + sell action |
| `ta.highest(high, 20)[1]` (function + bar offset) | `{ "kind": "func", ..., "offset": 1 }` |
| numeric literals (`20`, `0.001`, `70`) | `{ "kind": "literal", "value": "70" }` |

## What Cannot Map (hard limits of the swym DSL)

| PineScript feature | Why it can't map |
|---|---|
| **Nested indicator composition** — `ta.ema(ta.rsi(close, 14), 9)`, MACD signal line | `Func` takes a `CandleField`, not an `Expr`. Cannot feed one function's output into another. |
| **ATR / true range** — `ta.atr(14)` | Requires `max(high-low, abs(high-close[1]), abs(low-close[1]))` across bars — multi-field rolling function. |
| **MACD signal line** — `ta.macd(close, 12, 26, 9)` | MACD line itself (ema12-ema26) works via `BinOp`. Signal line (EMA of MACD) requires nested composition. |
| **Stochastic %D** — smoothed %K | Raw %K is expressible. %D (SMA of %K) requires nested composition. |
| **VWAP, OBV, MFI, ADX, Ichimoku, Parabolic SAR** | Each needs multi-field rolling or stateful accumulation not in the DSL. |
| **Dynamic position sizing** — `strategy.percent_of_equity`, Kelly, ATR-based | `quantity` is a fixed decimal, no access to equity or dynamic computation. |
| **Stop-loss / take-profit** — `strategy.exit(..., stop=, limit=)` | No limit/stop order types. Market orders only. |
| **Trailing stops** — `strategy.exit(..., trail_points=)` | Requires persistent state tracking highest/lowest since entry. |
| **Multi-timeframe** — `request.security(syminfo.tickerid, "1D", ta.sma(close, 50))` | Single timeframe only. |
| **Time/session filters** — `time(timeframe.period, "0930-1600")` | No timestamp access in the DSL. |
| **Bar counting** — `ta.barssince()`, `bar_index` | No bar counter or barssince function. |
| **Variables / state** — `var float trailStop = na` | No cross-bar variable storage. |
| **Pyramiding** — `strategy(..., pyramiding=3)` | No pyramiding control; rules fire every bar. |
| **Custom functions** — `f(x) => ...` | No user-defined functions. |
| **Arrays, matrices, maps** | Not in the DSL. |
| **Plotting / alerting** — `plot()`, `alertcondition()` | Not relevant to execution but converter should skip these. |

## Feasibility Assessment

### Tier 1: Highly feasible — "template strategies"
Strategies that use only: OHLCV fields, bar offsets, `ta.highest/lowest/sma/ema/rsi/stdev`, `ta.crossover/crossunder`, comparisons, arithmetic, boolean logic, `strategy.entry/close` with fixed quantity.

**Examples**: EMA crossover, RSI reversal, Bollinger breakout, Donchian channel, liquidity sweeps, mean reversion around SMA, simple momentum (ROC via `close/close[N]`).

**Estimated coverage**: ~30-40% of TradingView community scripts.

### Tier 2: Partially feasible — need DSL extensions
Strategies that additionally need: MACD (histogram only, not signal), Stochastic %K (not %D), basic ATR approximation, or dynamic sizing.

Some of these could be unlocked by extending `FuncName` to include `atr`, `macd_line`, `stoch_k` as first-class functions computed in Rust.

### Tier 3: Not feasible without major DSL redesign
Anything needing nested function composition, persistent state, multi-timeframe, limit/stop orders, or dynamic sizing. This is the majority of sophisticated TradingView strategies.

## Proposed Implementation: Tier 1 Converter

### Approach

A **TypeScript** converter in the dashboard (client-side) that:
1. Parses a subset of PineScript into an AST
2. Transforms the AST into swym DSL JSON
3. Shows the generated JSON in the AddStrategyPage form for review/editing before submission

This keeps it as a UI convenience tool — no backend changes needed. The user pastes PineScript, sees the generated swym JSON, tweaks if needed, and saves.

### Parser scope (PineScript subset)

The parser only needs to handle:
- **Variable declarations**: `x = expr` and `x = ta.func(...)` — extract as named expressions
- **`ta.*` function calls**: `ta.highest`, `ta.lowest`, `ta.sma`, `ta.ema`, `ta.rsi`, `ta.stdev`, `ta.crossover`, `ta.crossunder`
- **OHLCV built-ins**: `open`, `high`, `low`, `close`, `volume`
- **Bar offset operator**: `expr[N]`
- **Arithmetic**: `+`, `-`, `*`, `/`
- **Comparisons**: `>`, `<`, `>=`, `<=`, `==`
- **Boolean operators**: `and`, `or`, `not`
- **`strategy.entry()`**: extract name, direction, when condition
- **`strategy.close()`** / **`strategy.exit()`**: extract when condition
- **`strategy()` header**: extract `default_qty_value` for quantity
- **Numeric/string literals**
- **Comments** (`//`): skip
- **`input()` declarations**: extract default values
- **Ternary operator**: `cond ? a : b` — skip or warn

Everything else (plot, alertcondition, custom functions, arrays, for loops, if/else blocks, request.security, etc.) should be **ignored with a warning** rather than failing.

### Implementation plan

**New file**: `dashboard/src/lib/pinescript.ts` (~400-500 lines)

1. **Tokenizer** — regex-based, produces tokens: `IDENT`, `NUMBER`, `STRING`, `OP` (`+`,`-`,`*`,`/`,`>`,`<`,`>=`,`<=`,`==`,`!=`), `DOT`, `LPAREN`, `RPAREN`, `LBRACKET`, `RBRACKET`, `COMMA`, `ASSIGN`, `NEWLINE`, `AND`, `OR`, `NOT`, `WHEN`, `EOF`

2. **Parser** — recursive descent, produces a simple AST:
   ```
   Script = Statement[]
   Statement = VarDecl | StrategyHeader | StrategyEntry | StrategyClose | Ignored
   VarDecl = { name, expr }
   StrategyEntry = { label, direction, condition }
   StrategyClose = { label, condition }
   Expr = Literal | Ident | FuncCall | BinOp | UnaryOp | Subscript | Comparison | BoolOp
   ```

3. **Transformer** — walks the AST, resolves variable references (inline substitution), maps:
   - `strategy.entry("Long", strategy.long, when=cond)` → rule with `position: flat` + buy
   - `strategy.entry("Short", strategy.short, when=cond)` → rule with `position: flat` + sell
   - PineScript's implicit reversal (`strategy.entry` auto-closes opposite) → generates exit rule
   - `strategy.close("Long")` → rule with `position: long` + sell
   - `ta.crossover(a, b)` → `{ kind: "cross_over", left: transform(a), right: transform(b) }`
   - `ta.highest(field, period)[offset]` → `{ kind: "func", name: "highest", ..., offset }`
   - `a > b` → `{ kind: "compare", left: transform(a), op: ">", right: transform(b) }`
   - `a and b` → `{ kind: "all_of", conditions: [transform(a), transform(b)] }`
   - OHLCV idents → `{ kind: "field", field: name }`
   - numeric literal → `{ kind: "literal", value: "..." }`

4. **Error reporting** — unsupported constructs produce warnings (not fatal errors) with the line number and feature name. The converter emits as much as it can and flags what it skipped.

**Modified file**: `dashboard/src/pages/AddStrategyPage.tsx`

- Add a "PineScript" tab/toggle alongside the JSON editor
- PineScript textarea with a "Convert" button
- On convert: call `parsePineScript(source)` → get `{ rules, warnings, interval }`
- Populate the JSON textarea with the generated rules
- Show warnings below the PineScript input

### What this unlocks

The user pastes a TradingView strategy, gets swym JSON, reviews it, saves it as a strategy, and runs backtests. The conversion is transparent — the user always sees and can edit the generated JSON.

### Files to create/modify

| File | Action |
|---|---|
| `dashboard/src/lib/pinescript.ts` | New — tokenizer + parser + transformer |
| `dashboard/src/lib/pinescript.test.ts` | New — unit tests for parser and transformer |
| `dashboard/src/pages/AddStrategyPage.tsx` | Add PineScript tab with convert flow |

### Testing

- Unit tests in `pinescript.test.ts` covering:
  - Liquidity sweep strategy (known good conversion)
  - EMA crossover strategy
  - RSI reversal strategy
  - Bollinger breakout
  - Unsupported feature warnings (MACD signal, ATR, multi-timeframe)
  - Edge cases (empty script, comments-only, syntax errors)
- Manual: paste 5-10 popular TradingView strategies, verify output or clear warnings

## Verification

1. `pnpm tsc --noEmit` — type-checks
2. `pnpm test` (if vitest is set up) — unit tests pass
3. Manual: paste the MNQ Liquidity Sweep PineScript → verify it produces the exact same JSON as the hand-translated version
4. Manual: paste an EMA crossover PineScript → verify correct swym JSON
5. Manual: paste a MACD strategy → verify it converts what it can and warns about the signal line
