Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7a649ce
Add TradeMarkerBuilder with P/L-based exit coloring, delete vestigial…
bvasilenko Apr 11, 2026
a5cd903
Add trade-pair row striping and open-trade styling to TradeRowspanRen…
bvasilenko Apr 11, 2026
e494d25
Add fullscreen toggle with height-restore via inline style.height, dr…
bvasilenko Apr 18, 2026
608b567
Make Pine v4 strategies match TV with weekdays-only session default a…
bvasilenko May 30, 2026
1bfd072
Fix BB7 phantom weekend shorts by honoring Pine v4 session default DA…
bvasilenko Jun 2, 2026
e15ca08
Fix Pine `time()`, `time(period)`, and bare `time` builtin emitting b…
bvasilenko Jun 2, 2026
12e815a
Filter MOEX 06:00 MSK pre-session auction-print bars from the primary…
bvasilenko Jun 3, 2026
c2af15a
Q5L runner produces proper trade sizes, including crypto quantized lo…
bvasilenko Jun 13, 2026
7a32cf9
Pine UDF that reassigns a local only inside an if-branch now returns …
bvasilenko Jun 14, 2026
4df456e
now accepts a UDF value-expression evaluated per secondary bar, acce…
bvasilenko Jun 17, 2026
2f83f88
of `time(tf)` and `ta.change(time(tf))` boundaries now anchor to each…
bvasilenko Jun 17, 2026
1fbf987
on a coarser secondary timeframe now gates its value on the secondar…
bvasilenko Jun 18, 2026
9ed042b
Add per-bar runtime tests proving self-referential UDF-as-security va…
bvasilenko Jun 19, 2026
8508bff
resolve named entry quantity so zigzag emits real position sizes; re…
bvasilenko Jun 21, 2026
10fc0c5
Add fixture-quality preflight ratchet that rejects any wired TV-align…
bvasilenko Jun 21, 2026
621b273
Restore honest TV-alignment reporting and land the MOEX reference-ses…
bvasilenko Jul 10, 2026
81ac5c0
Consolidate order dispatch, single-source timestamp parsing, and pin …
bvasilenko Jul 10, 2026
e317190
Fix wma fractional-length weights to match TradingView integer-period…
bvasilenko Jul 10, 2026
d3a19a7
Update WMA weight test to the truncated-int period form
bvasilenko Jul 10, 2026
c09dbae
Handle typed drawing-object arrays gracefully instead of emitting unc…
bvasilenko Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 15 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ dist/
.pnp.*

# Generated files
chart-data.json
out/chart-data.json
out/chart-config.json
out/e2e-*-output.json
Expand Down Expand Up @@ -172,11 +173,16 @@ __pycache__/
*.so
.pyc

# GitHub - track CI workflows only
# GitHub - track CI workflows, reference time-series CSVs, and audit narratives
.github/*
!.github/workflows/
.github/workflows/local-*.yaml
.github/workflows/draft-*.yaml# Binaries for programs and plugins
.github/workflows/draft-*.yaml

# docs/ - internal working notes; never tracked (audit narratives carry internal reasoning)
docs/**

# Binaries for programs and plugins
*.exe
*.exe~
*.dll
Expand Down Expand Up @@ -230,3 +236,10 @@ testdata/*-output.json
# Contribot
contribot.*.json
transcripts/

.playwright-cli
# Editor and tool artifacts
*.backup

# Claude Code agent artifacts
.claude/scheduled_tasks.lock
129 changes: 129 additions & 0 deletions .interface-design/system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Design System

## File Structure

```
out/css/tokens.css — CSS custom properties (single source of truth for all tokens)
out/css/styles.css — component styles (@import ./tokens.css; zero hardcoded values)
out/index.html — <link rel="stylesheet" href="css/styles.css" />
```

Dependency graph: `index.html → styles.css → tokens.css`

## Tokens (`out/css/tokens.css`)

### Surfaces
| Token | Value | Usage |
|---|---|---|
| `--surface-base` | `#0f172a` | Deepest bg: body gradient start, table `<th>` |
| `--surface-card` | `#1a2035` | Section bg: `.trades-section`, `.info` |
| `--surface-raised` | `#1e293b` | Card bg: `#container`, `.chart-container` |
| `--surface-raised-alpha` | `rgba(30,41,59,0.85)` | Fullscreen toggle button bg |
| `--surface-hover` | `rgba(255,255,255,0.06)` | Row hover — visible on any dark surface |

### Borders
| Token | Value | Usage |
|---|---|---|
| `--border-subtle` | `#2d3748` | Inner dividers, row borders, section internals |
| `--border-default` | `#334155` | Outer borders: container, chart, buttons |

### Text
| Token | Value | Usage |
|---|---|---|
| `--text-primary` | `#cbd5e1` | Body text, card text |
| `--text-secondary` | `#94a3b8` | `<th>`, summary, button default label |
| `--text-muted` | `#6b7280` | Timestamp, `.no-trades` placeholder |

### Accent
| Token | Value | Usage |
|---|---|---|
| `--color-accent` | `#5eead4` | Section headings, hover state, focus ring |
| `--color-accent-alt` | `#2dd4bf` | h1 gradient end |
| `--color-accent-subtle` | `rgba(94,234,212,0.25)` | Pane resize handle hover fill |

### Actions
| Token | Value | Usage |
|---|---|---|
| `--color-action` | `#2563eb` | `.refresh-btn` bg |
| `--color-action-hover` | `#1d4ed8` | `.refresh-btn:hover` bg |

### Semantic
| Token | Value | Usage |
|---|---|---|
| `--color-long` | `#10b981` | Long direction, positive P/L |
| `--color-short` | `#ef4444` | Short direction, negative P/L |
| `--color-open` | `#3b82f6` | Open / in-progress trades |

### Trade Table
| Token | Value | Usage |
|---|---|---|
| `--stripe-tint` | `rgba(255,255,255,0.03)` | Alternating trade-pair row tint |

### Typography
| Token | Value | Usage |
|---|---|---|
| `--font-ui` | `'Segoe UI', system-ui, sans-serif` | All text |

### Border Radius
| Token | Value | Usage |
|---|---|---|
| `--radius-md` | `6px` | Most elements |
| `--radius-lg` | `8px` | Outer `#container` only |

### Shadows
| Token | Value | Usage |
|---|---|---|
| `--shadow-container` | `0 4px 6px -1px rgba(0,0,0,0.2)` | `#container` only |
| `--shadow-text` | `0 2px 4px rgba(0,0,0,0.3)` | `h1` text-shadow |

### Transitions
| Token | Value | Usage |
|---|---|---|
| `--transition-duration` | `0.2s` | All interactive elements |

## Spacing Scale
Rem-based: `0.5rem`, `0.75rem`, `1rem`, `1.25rem`, `1.5rem`, `2rem`, `2.5rem`
Pixel exceptions: `8px` (absolute inset positioning only)

## Font Scale
| Size | Usage |
|---|---|
| `2.5rem` | `h1` page title |
| `1.5rem` | `h2` section title |
| `1rem` | Body, action button |
| `0.875rem` | Table, timestamp, secondary labels, compact buttons |

Font weight `600`: profit values, table headers.

## Component Patterns

### Filled button (`.refresh-btn`)
- `background-color: var(--color-action)` → hover: `var(--color-action-hover)`
- `padding: 0.75rem 1.5rem`, `border-radius: var(--radius-md)`, `font-size: 1rem`
- `:focus-visible`: `outline: 2px solid var(--color-accent); outline-offset: 2px`
- `:active`: `transform: scale(0.97)`

### Ghost/outline button (`.sort-toggle-btn`, `.fullscreen-toggle-btn`)
- `background: transparent`, `border: 1px solid var(--border-default)`, `color: var(--text-secondary)`
- Hover: `border-color: var(--color-accent); color: var(--color-accent)`
- `:focus-visible`: same accent outline ring as filled button
- `:active`: `transform: scale(0.97)`

### Section card (`.trades-section`, `.info`)
- `background-color: var(--surface-card)`, `border: 1px solid var(--border-subtle)`, `border-radius: var(--radius-md)`, `padding: 1rem`

### Section header (`.trades-header`)
- `display: flex`, `justify-content: space-between`, `align-items: center`
- `margin-bottom: 1rem`, `padding-bottom: 0.5rem`, `border-bottom: 1px solid var(--border-subtle)`
- Title: `color: var(--color-accent)`

## Chart-specific (JS, not CSS)
Chart colors are LightweightCharts API options passed in `index.html` inline `<script>`.
They are intentionally separate from the CSS token system (different rendering context).

| Value | Usage |
|---|---|
| `#1e1e3c` | Chart layout background |
| `#DDD` | Chart text color |
| `#2B2B43` | Grid lines, scale borders |
| `#26a69a` / `#ef5350` | Candlestick up/down (TradingView convention) |
23 changes: 8 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Makefile for Runner - PineScript Go Port
# Centralized build automation following Go project conventions

.PHONY: help build test test-unit test-integration test-e2e test-golden test-golden-update test-parser test-codegen test-runtime test-series test-syminfo regression-syminfo bench bench-series coverage coverage-show check ci clean clean-all cross-compile fmt vet lint build-strategy
.PHONY: help build test test-unit test-integration test-e2e test-golden test-golden-update test-parser test-codegen test-runtime test-series test-syminfo regression-syminfo test-ui bench bench-series coverage coverage-show check ci clean clean-all cross-compile fmt vet lint build-strategy

# Project configuration
PROJECT_NAME := runner
Expand Down Expand Up @@ -161,6 +161,11 @@ test-syminfo: ## Run syminfo.tickerid integration tests only
test-syminfo-regression: ## Run syminfo.tickerid regression test suite
@./scripts/test-syminfo-regression.sh

test-ui: ## Run chart viewer JS unit tests (node:test, no install required)
@echo "Running chart viewer JS tests..."
@node --test out/tests/*.test.js
@echo "✓ Chart UI tests passed"

bench: ## Run benchmarks
@echo "Running benchmarks..."
@ $(GO) test $(BENCH_FLAGS) -bench=. ./...
Expand Down Expand Up @@ -356,21 +361,9 @@ all: ci ## Full validation (format, vet, lint, build, all tests)

install-hooks: ## Install git pre-commit hook
@echo "Installing pre-commit hook..."
@echo '#!/bin/sh' > .git/hooks/pre-commit
@echo '# Git pre-commit hook - full validation' >> .git/hooks/pre-commit
@echo 'set -e' >> .git/hooks/pre-commit
@echo 'export PATH="$$HOME/.local/go/bin:/usr/local/go/bin:$$PATH"' >> .git/hooks/pre-commit
@echo 'export GOPATH="$$HOME/go"' >> .git/hooks/pre-commit
@echo 'export PATH="$$PATH:$$GOPATH/bin"' >> .git/hooks/pre-commit
@echo 'if ! command -v go >/dev/null 2>&1; then' >> .git/hooks/pre-commit
@echo ' echo "✗ Go not found. Run: make install"' >> .git/hooks/pre-commit
@echo ' exit 1' >> .git/hooks/pre-commit
@echo 'fi' >> .git/hooks/pre-commit
@echo 'echo "🔍 Running pre-commit validation..."' >> .git/hooks/pre-commit
@echo 'make all' >> .git/hooks/pre-commit
@echo 'exit 0' >> .git/hooks/pre-commit
@cp scripts/pre-commit-hook.sh .git/hooks/pre-commit
@chmod +x .git/hooks/pre-commit
@echo "✓ Pre-commit hook installed (runs: make all)"
@echo "✓ Pre-commit hook installed"

install: ## Install Go to ~/.local (no sudo required)
@./scripts/install-deps.sh
Expand Down
12 changes: 12 additions & 0 deletions cmd/pine-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ func main() {
os.Exit(1)
}

reportFeatureGaps(strategyCode.FeatureGaps)

temporaryDirectory := os.TempDir()

/* Create unique temp file to avoid conflicts when running tests in parallel */
Expand All @@ -141,6 +143,16 @@ func main() {
fmt.Printf("Next: Compile with: go build -o %s %s\n", *outputFlag, temporaryGoFile)
}

func reportFeatureGaps(gaps []string) {
if len(gaps) == 0 {
return
}
fmt.Fprintf(os.Stderr, "WARNING: %d unimplemented Pine function(s) — binary runs with stubs:\n", len(gaps))
for _, name := range gaps {
fmt.Fprintf(os.Stderr, " - %s()\n", name)
}
}

func deriveStrategyNameFromSourceFile(inputPath string) string {
baseFilename := filepath.Base(inputPath)
extension := filepath.Ext(baseFilename)
Expand Down
15 changes: 9 additions & 6 deletions codegen/argument_expression_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,22 @@ func (g *ArgumentExpressionGenerator) ensureFloat64(expr ast.Expression, code st
}

func (g *ArgumentExpressionGenerator) generateIdentifier(id *ast.Identifier) (string, error) {
expectsSeries := false
if g.signatureRegistry != nil {
paramType, hasSignature := g.signatureRegistry.GetParameterType(g.functionName, g.parameterIndex)
expectsSeries = hasSignature && paramType == ParamTypeSeries
}

if constVal, isConstant := g.generator.constants[id.Name]; isConstant {
if constVal == "input.source" {
if expectsSeries {
return fmt.Sprintf("%sSeries", id.Name), nil
}
return fmt.Sprintf("%sSeries.GetCurrent()", id.Name), nil
}
return id.Name, nil
}

expectsSeries := false
if g.signatureRegistry != nil {
paramType, hasSignature := g.signatureRegistry.GetParameterType(g.functionName, g.parameterIndex)
expectsSeries = hasSignature && paramType == ParamTypeSeries
}

// Arrow resolver checked before builtins; bypassed when passing to a series-typed parameter
// so the identifier routes to *Series by name convention instead of scalar resolution.
if !expectsSeries && g.generator.arrowAccessResolver != nil {
Expand Down
4 changes: 1 addition & 3 deletions codegen/argument_extractor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,7 @@ func TestExtractNamedOrPositional_UseDefault(t *testing.T) {
}

func TestExtractWhenCondition_Found(t *testing.T) {
g := &generator{
builtinHandler: NewBuiltinIdentifierHandler(),
}
g := newTestGenerator()
extractor := &ArgumentExtractor{generator: g}

args := []ast.Expression{
Expand Down
15 changes: 14 additions & 1 deletion codegen/argument_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,27 @@ func (p *ArgumentParser) ParseInt(expr ast.Expression) ParsedArgument {

/*
ParseFloat extracts a float literal from an AST expression.
Handles both float64 and int AST literal types.
Handles float64, int, and UnaryExpression for negative numbers.

Returns:

ParsedArgument.IsValid = true if numeric literal found
ParsedArgument.Value = float64 value
*/
func (p *ArgumentParser) ParseFloat(expr ast.Expression) ParsedArgument {
if unary, ok := expr.(*ast.UnaryExpression); ok && unary.Operator == "-" {
inner := p.ParseFloat(unary.Argument)
if inner.IsValid {
return ParsedArgument{
IsValid: true,
IsLiteral: true,
Value: -inner.MustBeFloat(),
SourceExpr: expr,
}
}
return ParsedArgument{IsValid: false, SourceExpr: expr}
}

lit, ok := expr.(*ast.Literal)
if !ok {
return ParsedArgument{IsValid: false, SourceExpr: expr}
Expand Down
Loading
Loading