mirror of
https://github.com/manifoldbt/manifoldbt.git
synced 2026-08-24 14:38:04 +00:00
release: v0.13.1
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "manifoldbt"
|
name = "manifoldbt"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
description = "Rust-powered backtesting engine for quantitative research"
|
description = "Rust-powered backtesting engine for quantitative research"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
|
|||||||
@@ -214,8 +214,11 @@ def tearsheet(
|
|||||||
|
|
||||||
# ── plotly.js include ─────────────────────────────────────────
|
# ── plotly.js include ─────────────────────────────────────────
|
||||||
if plotlyjs == "inline":
|
if plotlyjs == "inline":
|
||||||
import plotly.io as pio
|
# get_plotlyjs lives in plotly.offline. plotly.io has never exposed it
|
||||||
plotly_js_tag = f"<script>{pio.get_plotlyjs()}</script>"
|
# (checked on 5.24 and 6.9), so the old plotly.io lookup raised
|
||||||
|
# AttributeError on every inline report rather than embedding anything.
|
||||||
|
from plotly.offline import get_plotlyjs
|
||||||
|
plotly_js_tag = f"<script>{get_plotlyjs()}</script>"
|
||||||
else:
|
else:
|
||||||
plotly_js_tag = '<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>'
|
plotly_js_tag = '<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>'
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
"""Tearsheet rendering, in particular the offline (``plotlyjs="inline"``) report.
|
||||||
|
|
||||||
|
The inline path was written against ``plotly.io.get_plotlyjs``, which does not
|
||||||
|
exist and never has, so every offline report raised AttributeError. Nothing
|
||||||
|
exercised it, so nothing caught it. These tests render both modes for real.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import manifoldbt as bt
|
||||||
|
from manifoldbt import run_with_parquet
|
||||||
|
|
||||||
|
pytest.importorskip("plotly")
|
||||||
|
|
||||||
|
tearsheet = pytest.importorskip("manifoldbt.plot.tearsheet").tearsheet
|
||||||
|
|
||||||
|
# Roughly the size of the embedded plotly runtime; the point is to tell an
|
||||||
|
# actually-embedded bundle from a one-line CDN script tag, not to pin a version.
|
||||||
|
_RUNTIME_BYTES = 2_000_000
|
||||||
|
_CDN_TAG = '<script src="https://cdn.plot.ly'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def backtest_result(golden_buy_hold_dir):
|
||||||
|
strategy = bt.Strategy(
|
||||||
|
name="tearsheet_probe",
|
||||||
|
signals={"signal": bt.lit(1.0)},
|
||||||
|
position_sizing=bt.col("signal"),
|
||||||
|
)
|
||||||
|
config = bt.BacktestConfig(
|
||||||
|
universe=[1],
|
||||||
|
time_range_start=0,
|
||||||
|
time_range_end=4_000_000_000,
|
||||||
|
bar_interval={"Days": 1},
|
||||||
|
initial_capital=1000.0,
|
||||||
|
currency="USD",
|
||||||
|
execution=bt.ExecutionConfig(
|
||||||
|
signal_delay=1,
|
||||||
|
execution_price="AtClose",
|
||||||
|
max_position_pct=1.0,
|
||||||
|
allow_short=False,
|
||||||
|
allow_fractional=True,
|
||||||
|
skip_gap_bars=False,
|
||||||
|
position_sizing_mode="Units",
|
||||||
|
),
|
||||||
|
fees=bt.FeeConfig(),
|
||||||
|
slippage={"FixedBps": {"bps": 0.0}},
|
||||||
|
data_version="golden_v1",
|
||||||
|
rng_seed=7,
|
||||||
|
)
|
||||||
|
return run_with_parquet(
|
||||||
|
strategy.to_json(),
|
||||||
|
config.to_json(),
|
||||||
|
os.path.join(golden_buy_hold_dir, "bars_1m.parquet"),
|
||||||
|
"golden_v1",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inline_report_embeds_the_runtime(backtest_result, tmp_path):
|
||||||
|
out = tmp_path / "inline.html"
|
||||||
|
tearsheet(backtest_result, save=str(out), show=False, plotlyjs="inline")
|
||||||
|
|
||||||
|
body = out.read_text(encoding="utf-8")
|
||||||
|
assert _CDN_TAG not in body, "inline report still loads plotly from the CDN"
|
||||||
|
assert out.stat().st_size > _RUNTIME_BYTES, (
|
||||||
|
f"inline report is only {out.stat().st_size} bytes, "
|
||||||
|
"the plotly runtime does not look embedded"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cdn_report_links_the_runtime_instead_of_embedding_it(
|
||||||
|
backtest_result, tmp_path
|
||||||
|
):
|
||||||
|
out = tmp_path / "cdn.html"
|
||||||
|
tearsheet(backtest_result, save=str(out), show=False, plotlyjs="cdn")
|
||||||
|
|
||||||
|
body = out.read_text(encoding="utf-8")
|
||||||
|
assert _CDN_TAG in body
|
||||||
|
assert out.stat().st_size < _RUNTIME_BYTES
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_plotlyjs_comes_from_plotly_offline():
|
||||||
|
"""Pin the import location that the inline path depends on."""
|
||||||
|
import plotly.io as pio
|
||||||
|
from plotly.offline import get_plotlyjs
|
||||||
|
|
||||||
|
assert not hasattr(pio, "get_plotlyjs"), (
|
||||||
|
"plotly.io grew a get_plotlyjs; the tearsheet import can be simplified"
|
||||||
|
)
|
||||||
|
assert len(get_plotlyjs()) > _RUNTIME_BYTES
|
||||||
Reference in New Issue
Block a user