bruedemo 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bruedemo-0.1.0/.gitignore +2 -0
- bruedemo-0.1.0/ARCHITECTURE.md +83 -0
- bruedemo-0.1.0/PKG-INFO +20 -0
- bruedemo-0.1.0/PORTING.md +37 -0
- bruedemo-0.1.0/README.md +12 -0
- bruedemo-0.1.0/brue/__init__.py +65 -0
- bruedemo-0.1.0/brue/__main__.py +5 -0
- bruedemo-0.1.0/brue/api.py +182 -0
- bruedemo-0.1.0/brue/ast.py +319 -0
- bruedemo-0.1.0/brue/builtins.py +2280 -0
- bruedemo-0.1.0/brue/cli.py +369 -0
- bruedemo-0.1.0/brue/data.py +630 -0
- bruedemo-0.1.0/brue/errors.py +74 -0
- bruedemo-0.1.0/brue/indicators.py +2411 -0
- bruedemo-0.1.0/brue/jsmath.py +551 -0
- bruedemo-0.1.0/brue/jsnum.py +124 -0
- bruedemo-0.1.0/brue/output.py +626 -0
- bruedemo-0.1.0/brue/parser.py +1263 -0
- bruedemo-0.1.0/brue/quant.py +524 -0
- bruedemo-0.1.0/brue/runtime.py +2691 -0
- bruedemo-0.1.0/brue/session.py +234 -0
- bruedemo-0.1.0/brue/strategy.py +1017 -0
- bruedemo-0.1.0/brue/tokenizer.py +395 -0
- bruedemo-0.1.0/brue/value.py +285 -0
- bruedemo-0.1.0/conformance/run_all_py.sh +47 -0
- bruedemo-0.1.0/pyproject.toml +14 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Brue's job: the decision layer
|
|
2
|
+
|
|
3
|
+
One sentence: given bars, Brue decides what a strategy does, and it makes
|
|
4
|
+
those decisions identically everywhere. Everything else in a trading
|
|
5
|
+
pipeline belongs to something else.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
DATA -> BRUE -> EXECUTION -> RESULTS
|
|
9
|
+
pandas / feeds / the strategy backtest: simulated stats, trade
|
|
10
|
+
broker bars / csv brain fills inside Brue ledger, equity
|
|
11
|
+
live: order intents -> broker adapter
|
|
12
|
+
(user's account)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What Brue OWNS
|
|
16
|
+
|
|
17
|
+
1. **Indicators.** ema, rsi, atr and the rest of the 143 builtins, computed
|
|
18
|
+
with fixed, conformance-locked semantics. Same inputs, same bits, on
|
|
19
|
+
every implementation.
|
|
20
|
+
2. **Strategy logic.** Entries, exits, conditions, long/short. The part a
|
|
21
|
+
trader actually thinks about, written in a language built for exactly
|
|
22
|
+
that.
|
|
23
|
+
3. **Position rules.** Sizing (including risk-based sizing off stop
|
|
24
|
+
distance), stops, targets, trailing stops, commission.
|
|
25
|
+
4. **In backtest mode: the simulator.** Next-bar-open fills (an order
|
|
26
|
+
placed on the signal bar fills at the NEXT bar's open, never the signal
|
|
27
|
+
bar), bracket handling, and the full stats block. This is where "same
|
|
28
|
+
script, same numbers" is enforced.
|
|
29
|
+
5. **In live mode: signals only.** The same logic evaluates on each new
|
|
30
|
+
bar and emits order INTENTS ("enter long, 2% risk, stop here"). Brue
|
|
31
|
+
never sends the order itself.
|
|
32
|
+
|
|
33
|
+
## What Brue deliberately does NOT do
|
|
34
|
+
|
|
35
|
+
- It never fetches data. Bars come in from the host (pandas frame, csv,
|
|
36
|
+
a feed, a broker terminal).
|
|
37
|
+
- It never holds broker credentials.
|
|
38
|
+
- It never talks to a broker. Executing an intent is the host's job,
|
|
39
|
+
through whatever adapter the user connects with their own account.
|
|
40
|
+
- It never touches the filesystem or network from inside a script. A
|
|
41
|
+
strategy is pure logic over bars, which is what makes it safe to run in
|
|
42
|
+
a sandbox, in a browser, or on someone else's machine.
|
|
43
|
+
|
|
44
|
+
That boundary is the product: the strategy is a portable artifact. The
|
|
45
|
+
same script runs in the site editor, the cloud notebook, the desktop
|
|
46
|
+
terminal, a Python session via `brue.backtest(script, df)`, and (once the
|
|
47
|
+
live driver ships) against any connected broker, and every one of those
|
|
48
|
+
produces bit-identical decisions because they are all the same
|
|
49
|
+
conformance-tested engine. The pipe around the strategy is swappable; the
|
|
50
|
+
strategy is yours.
|
|
51
|
+
|
|
52
|
+
## How the pieces line up in Python
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import pandas as pd # data: pandas' job
|
|
56
|
+
import brue # decisions: Brue's job
|
|
57
|
+
|
|
58
|
+
df = pd.read_csv("EURJPY_1h.csv")
|
|
59
|
+
result = brue.backtest(SCRIPT, df, extended_stats=True)
|
|
60
|
+
result.stats # dict of the canonical stats block
|
|
61
|
+
result.trades_frame() # trade ledger as a DataFrame
|
|
62
|
+
result.equity_frame() # per-bar equity with timestamps
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The engine core under this API is a 1:1 port verified byte-for-byte
|
|
66
|
+
against the reference implementation over the full conformance corpus
|
|
67
|
+
(see PORTING.md for the rules and the verification ladder).
|
|
68
|
+
|
|
69
|
+
## What still needs to be built (in order)
|
|
70
|
+
|
|
71
|
+
1. **Package release.** Publish to PyPI so `pip install` works outside
|
|
72
|
+
this machine. (Name decision pending.)
|
|
73
|
+
2. **Live driver.** The streaming counterpart of `backtest()`: feed bars
|
|
74
|
+
one at a time, get order intents out through a callback, with
|
|
75
|
+
semantics provably identical to the backtest of the same window.
|
|
76
|
+
3. **Broker adapters.** A thin adapter contract that turns intents into
|
|
77
|
+
orders on the user's own account, paper-trading adapter first, then
|
|
78
|
+
real ones. Credentials always stay on the user's machine.
|
|
79
|
+
4. **Host integrations.** The desktop terminal and notebook surfaces move
|
|
80
|
+
onto this engine so every surface runs the same code.
|
|
81
|
+
5. **Spec + public conformance suite.** The rulebook and test corpus
|
|
82
|
+
published so third-party tooling can target the language without
|
|
83
|
+
semantic drift.
|
bruedemo-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bruedemo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Internal test build of the Brue trading-strategy language engine. Will be republished under its final name; do not depend on this package.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# bruedemo
|
|
10
|
+
|
|
11
|
+
Internal TEST build of the Brue trading-strategy language engine
|
|
12
|
+
(pure Python). Published for real-world install testing by the
|
|
13
|
+
London Strategic Edge team only. The engine will be republished under
|
|
14
|
+
its final name; do not depend on this package.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import brue
|
|
18
|
+
|
|
19
|
+
result = brue.backtest(script, dataframe)
|
|
20
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# brue-py: 1:1 Python port of brue-rust (owner decision 2026-07-21: the
|
|
2
|
+
# shipping Brue engine is PYTHON; the Rust tree becomes the porting source).
|
|
3
|
+
|
|
4
|
+
RULES FOR EVERY PORTED MODULE
|
|
5
|
+
- Source of truth: /opt/lse-worker/brue-rust/src/<name>.rs. Port line by
|
|
6
|
+
line; keep the SAME module name (value.rs -> brue/value.py) and the SAME
|
|
7
|
+
function/struct names (Rust is snake_case already; structs become
|
|
8
|
+
dataclasses or plain classes with identical field names).
|
|
9
|
+
- Semantics are the product. Preserve every documented quirk (na == na is
|
|
10
|
+
true, and/or evaluate both sides, division by zero -> na, compound-assign
|
|
11
|
+
coercion order, EMA SMA-seeding, float accumulation ORDER in indicators,
|
|
12
|
+
reference equality for colors/arrays/maps, byte-identical error messages
|
|
13
|
+
including "did you mean" tie-breaks).
|
|
14
|
+
- Numbers: Python floats are IEEE754 doubles like Rust f64; keep operation
|
|
15
|
+
order identical. Never "simplify" running sums into vectorized forms.
|
|
16
|
+
- jsmath/jsnum are V8-exact; port the fdlibm bit-twiddling as-is using
|
|
17
|
+
struct.pack/unpack for bit casts.
|
|
18
|
+
- No third-party imports in the engine (stdlib only). numpy is BANNED in
|
|
19
|
+
engine modules (accumulation-order parity).
|
|
20
|
+
- Comments: keep the Rust file's explanatory comments where they explain
|
|
21
|
+
WHY. Never use em dashes or en dashes anywhere.
|
|
22
|
+
- Cross-module imports: from . import jsmath (etc.); keep function names so
|
|
23
|
+
parallel porting cannot desync interfaces.
|
|
24
|
+
- Every module ends with nothing else: no __main__ blocks except cli.py.
|
|
25
|
+
|
|
26
|
+
LAYOUT
|
|
27
|
+
brue/value.py errors.py jsnum.py jsmath.py tokenizer.py ast.py parser.py
|
|
28
|
+
indicators.py session.py data.py output.py builtins.py runtime.py
|
|
29
|
+
strategy.py quant.py cli.py __init__.py (__main__.py -> cli.main)
|
|
30
|
+
|
|
31
|
+
VERIFICATION LADDER
|
|
32
|
+
1. Module-level checks where possible (jsmath vs node mathcheck samples,
|
|
33
|
+
tokenizer/parser over the conformance corpus).
|
|
34
|
+
2. Full conformance: conformance/run_all_py.sh compares `python -m brue run`
|
|
35
|
+
output against the RUST binary (itself 138/138 vs the TS reference),
|
|
36
|
+
NaN/Inf-exact, over corpus x generated datasets. The port is DONE only
|
|
37
|
+
when every case matches byte-for-byte.
|
bruedemo-0.1.0/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# bruedemo
|
|
2
|
+
|
|
3
|
+
Internal TEST build of the Brue trading-strategy language engine
|
|
4
|
+
(pure Python). Published for real-world install testing by the
|
|
5
|
+
London Strategic Edge team only. The engine will be republished under
|
|
6
|
+
its final name; do not depend on this package.
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
import brue
|
|
10
|
+
|
|
11
|
+
result = brue.backtest(script, dataframe)
|
|
12
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""brue: pure-Python implementation of the Brue trading-strategy language.
|
|
2
|
+
|
|
3
|
+
Ported 1:1 from the Rust engine (src/lib.rs); the conformance harness in
|
|
4
|
+
conformance/ verifies byte-identical output against it.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_script(source, bars):
|
|
11
|
+
from .runtime import ExternalData
|
|
12
|
+
return run_script_with_data(source, bars, ExternalData())
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_script_with_data(source, bars, external):
|
|
16
|
+
from .runtime import RunOptions
|
|
17
|
+
return run_script_full(source, bars, external, {}, RunOptions())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_script_full(source, bars, external, input_overrides, options):
|
|
21
|
+
"""tokenize + parse + execute, errors shaped exactly like the reference."""
|
|
22
|
+
from . import parser, tokenizer
|
|
23
|
+
from .errors import BrueError
|
|
24
|
+
|
|
25
|
+
# Windows editors and Python text-mode writes produce CRLF; the
|
|
26
|
+
# tokenizer speaks LF. Normalize at every entry point.
|
|
27
|
+
source = source.replace("\r\n", "\n").replace("\r", "\n")
|
|
28
|
+
try:
|
|
29
|
+
tokens = tokenizer.tokenize(source)
|
|
30
|
+
except BrueError as e:
|
|
31
|
+
return _error_output(e)
|
|
32
|
+
try:
|
|
33
|
+
program = parser.parse(tokens)
|
|
34
|
+
except BrueError as e:
|
|
35
|
+
return _error_output(e)
|
|
36
|
+
from .runtime import execute_with_options
|
|
37
|
+
return execute_with_options(program, bars, input_overrides, external, options)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# parse_time_arg and parse_bars_csv live in data.py, ported with Rust-exact
|
|
41
|
+
# parsing (float()/int() alone are looser than f64::from_str: underscores,
|
|
42
|
+
# unicode digits); re-exported here for the lib.rs-shaped public API.
|
|
43
|
+
from .data import parse_bars_csv, parse_time_arg # noqa: E402,F401
|
|
44
|
+
from .api import BacktestResult, BrueScriptError, backtest # noqa: E402,F401
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _error_output(e):
|
|
48
|
+
from .output import ErrorInfo, ExecOutput
|
|
49
|
+
from .value import Value
|
|
50
|
+
return ExecOutput(
|
|
51
|
+
success=False,
|
|
52
|
+
commands=[],
|
|
53
|
+
errors=[ErrorInfo(message=e.message, line=e.loc.line, col=e.loc.col,
|
|
54
|
+
phase=e.phase, suggestion=e.suggestion)],
|
|
55
|
+
inputs=[],
|
|
56
|
+
live_orders=[],
|
|
57
|
+
live_closes=[],
|
|
58
|
+
decl_title=Value.Null,
|
|
59
|
+
decl_overlay=Value.Bool(True),
|
|
60
|
+
decl_symbol=None,
|
|
61
|
+
decl_timeframe=None,
|
|
62
|
+
strategy_result=None,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""The Python-native front door for the brue package.
|
|
2
|
+
|
|
3
|
+
The engine internals speak BarData lists and ExecOutput (ported 1:1 from
|
|
4
|
+
the reference engine; conformance-locked, do not touch). This module is the
|
|
5
|
+
convenience layer a Python user actually calls:
|
|
6
|
+
|
|
7
|
+
import brue
|
|
8
|
+
|
|
9
|
+
result = brue.backtest(script, df) # df: pandas or rows
|
|
10
|
+
result.stats["netProfit"], result.trades, result.equity_curve
|
|
11
|
+
|
|
12
|
+
Accepted data shapes: a pandas DataFrame (any case-insensitive subset of
|
|
13
|
+
time/open/high/low/close/volume columns, time in epoch s, epoch ms, or
|
|
14
|
+
datetimes), a list of dicts with those keys, a list of [t,o,h,l,c,v] rows,
|
|
15
|
+
or a path to a csv. pandas is NOT a dependency: it is duck-typed, so the
|
|
16
|
+
package installs clean without it.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BrueScriptError(Exception):
|
|
24
|
+
"""The script failed to tokenize/parse/run; message is user-facing."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, errors):
|
|
27
|
+
self.errors = errors
|
|
28
|
+
first = errors[0] if errors else {}
|
|
29
|
+
super().__init__(first.get("message", "script error"))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BacktestResult:
|
|
33
|
+
"""Friendly view over the engine's canonical output."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, raw: dict, bar_times=None):
|
|
36
|
+
self.raw = raw
|
|
37
|
+
sr = raw.get("strategyResult") or {}
|
|
38
|
+
self.stats = {k: v for k, v in sr.items()
|
|
39
|
+
if k not in ("trades", "equityCurve")}
|
|
40
|
+
self.trades = sr.get("trades", [])
|
|
41
|
+
# The engine emits one equity value per bar, no timestamps; the
|
|
42
|
+
# caller's bar times restore the x axis.
|
|
43
|
+
self.equity_curve = sr.get("equityCurve", [])
|
|
44
|
+
self.bar_times = bar_times or []
|
|
45
|
+
|
|
46
|
+
def trades_frame(self):
|
|
47
|
+
"""The trade ledger as a pandas DataFrame (pandas required here)."""
|
|
48
|
+
import pandas as pd
|
|
49
|
+
return pd.DataFrame(self.trades)
|
|
50
|
+
|
|
51
|
+
def equity_frame(self):
|
|
52
|
+
import pandas as pd
|
|
53
|
+
df = pd.DataFrame({"equity": self.equity_curve})
|
|
54
|
+
if len(self.bar_times) == len(df):
|
|
55
|
+
df.insert(0, "time", pd.to_datetime(self.bar_times, unit="ms"))
|
|
56
|
+
return df
|
|
57
|
+
|
|
58
|
+
def __repr__(self):
|
|
59
|
+
s = self.stats
|
|
60
|
+
return (f"BacktestResult(trades={s.get('totalTrades')}, "
|
|
61
|
+
f"netProfit={s.get('netProfit')}, "
|
|
62
|
+
f"winRate={s.get('winRate')}, "
|
|
63
|
+
f"profitFactor={s.get('profitFactor')})")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _to_ms(t) -> float:
|
|
67
|
+
"""Epoch seconds, epoch ms, or datetime-like -> epoch ms."""
|
|
68
|
+
if hasattr(t, "timestamp"):
|
|
69
|
+
return float(t.timestamp() * 1000.0)
|
|
70
|
+
x = float(t)
|
|
71
|
+
# Heuristic shared with the userdata importer: values before the year
|
|
72
|
+
# 5138 in seconds are seconds; anything bigger is already ms.
|
|
73
|
+
return x * 1000.0 if x < 1e11 else x
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _bars_from_any(data):
|
|
77
|
+
from .runtime import BarData
|
|
78
|
+
|
|
79
|
+
if isinstance(data, str):
|
|
80
|
+
from . import parse_bars_csv
|
|
81
|
+
with open(data, encoding="utf-8") as f:
|
|
82
|
+
return parse_bars_csv(f.read())
|
|
83
|
+
|
|
84
|
+
# pandas DataFrame, duck-typed so pandas stays optional.
|
|
85
|
+
if hasattr(data, "columns") and hasattr(data, "itertuples"):
|
|
86
|
+
cols = {str(c).strip().lower(): c for c in data.columns}
|
|
87
|
+
tcol = next((cols[k] for k in ("time", "ts", "timestamp", "date",
|
|
88
|
+
"datetime") if k in cols), None)
|
|
89
|
+
need = [k for k in ("open", "high", "low", "close") if k not in cols]
|
|
90
|
+
if tcol is None or need:
|
|
91
|
+
missing = (["time"] if tcol is None else []) + need
|
|
92
|
+
raise ValueError(f"data is missing columns: {', '.join(missing)}")
|
|
93
|
+
# Follow the index when there is no time column? No: explicit only,
|
|
94
|
+
# a wrong guess silently shifts every fill.
|
|
95
|
+
out = []
|
|
96
|
+
vol = cols.get("volume")
|
|
97
|
+
for row in data.itertuples(index=False):
|
|
98
|
+
d = dict(zip(data.columns, row))
|
|
99
|
+
out.append(BarData(
|
|
100
|
+
time=_to_ms(d[tcol]),
|
|
101
|
+
open=float(d[cols["open"]]), high=float(d[cols["high"]]),
|
|
102
|
+
low=float(d[cols["low"]]), close=float(d[cols["close"]]),
|
|
103
|
+
volume=float(d[vol]) if vol is not None else 0.0))
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
if isinstance(data, (list, tuple)) and data:
|
|
107
|
+
first = data[0]
|
|
108
|
+
if isinstance(first, dict):
|
|
109
|
+
return [BarData(time=_to_ms(r.get("time", r.get("ts", r.get("timestamp")))),
|
|
110
|
+
open=float(r["open"]), high=float(r["high"]),
|
|
111
|
+
low=float(r["low"]), close=float(r["close"]),
|
|
112
|
+
volume=float(r.get("volume", 0.0)))
|
|
113
|
+
for r in data]
|
|
114
|
+
if isinstance(first, (list, tuple)) and len(first) >= 5:
|
|
115
|
+
return [BarData(time=_to_ms(r[0]), open=float(r[1]), high=float(r[2]),
|
|
116
|
+
low=float(r[3]), close=float(r[4]),
|
|
117
|
+
volume=float(r[5]) if len(r) > 5 else 0.0)
|
|
118
|
+
for r in data]
|
|
119
|
+
raise ValueError("unsupported data shape: pass a DataFrame, list of "
|
|
120
|
+
"dicts, list of [t,o,h,l,c,v] rows, or a csv path")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def indicator_series(script: str, data, inputs: dict | None = None) -> dict:
|
|
124
|
+
"""
|
|
125
|
+
Run a Brue script as a chart INDICATOR: every plot() call becomes a
|
|
126
|
+
named series aligned to the bars. This is how hosts (the LSE Terminal,
|
|
127
|
+
notebooks) draw Brue-written indicators on live charts.
|
|
128
|
+
|
|
129
|
+
Returns {"plots": {name: [float per bar]}, "meta": {name: kwargs},
|
|
130
|
+
"overlay": bool, "inputs": [declared input() specs],
|
|
131
|
+
"times": [epoch ms per bar]}.
|
|
132
|
+
Raises BrueScriptError on script errors.
|
|
133
|
+
"""
|
|
134
|
+
from . import run_script_full
|
|
135
|
+
from .runtime import ExternalData, RunOptions
|
|
136
|
+
from .value import Value
|
|
137
|
+
|
|
138
|
+
bars = _bars_from_any(data)
|
|
139
|
+
options = RunOptions()
|
|
140
|
+
options.collect_plots = True
|
|
141
|
+
overrides = {}
|
|
142
|
+
for k, v in (inputs or {}).items():
|
|
143
|
+
overrides[str(k)] = (Value.Bool(v) if isinstance(v, bool)
|
|
144
|
+
else Value.Num(v) if isinstance(v, (int, float))
|
|
145
|
+
else Value.Str(str(v)))
|
|
146
|
+
out = run_script_full(script, bars, ExternalData(), overrides, options)
|
|
147
|
+
raw = json.loads(out.to_json())
|
|
148
|
+
if not raw.get("success"):
|
|
149
|
+
raise BrueScriptError(raw.get("errors", []))
|
|
150
|
+
n = len(bars)
|
|
151
|
+
plots = {}
|
|
152
|
+
for name, points in getattr(out, "plots", {}).items():
|
|
153
|
+
col = [float("nan")] * n
|
|
154
|
+
for bar_i, v in points:
|
|
155
|
+
if 0 <= bar_i < n:
|
|
156
|
+
col[bar_i] = v
|
|
157
|
+
plots[name] = col
|
|
158
|
+
return {"plots": plots,
|
|
159
|
+
"meta": getattr(out, "plot_meta", {}),
|
|
160
|
+
"overlay": bool((raw.get("declaration") or {}).get("overlay", True)),
|
|
161
|
+
"inputs": raw.get("inputs", []),
|
|
162
|
+
"times": [b.time for b in bars]}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def backtest(script: str, data, extended_stats: bool = False,
|
|
166
|
+
trade_from=None, trade_to=None) -> BacktestResult:
|
|
167
|
+
"""Run a Brue strategy over the given bars, entirely in-process."""
|
|
168
|
+
from . import run_script_full
|
|
169
|
+
from .runtime import ExternalData, RunOptions
|
|
170
|
+
|
|
171
|
+
bars = _bars_from_any(data)
|
|
172
|
+
options = RunOptions()
|
|
173
|
+
options.extended_stats = bool(extended_stats)
|
|
174
|
+
if trade_from is not None:
|
|
175
|
+
options.trade_from = _to_ms(trade_from)
|
|
176
|
+
if trade_to is not None:
|
|
177
|
+
options.trade_to = _to_ms(trade_to)
|
|
178
|
+
out = run_script_full(script, bars, ExternalData(), {}, options)
|
|
179
|
+
raw = json.loads(out.to_json())
|
|
180
|
+
if not raw.get("success"):
|
|
181
|
+
raise BrueScriptError(raw.get("errors", []))
|
|
182
|
+
return BacktestResult(raw, bar_times=[b.time for b in bars])
|