@tangle-network/agent-bench 0.3.6 → 0.3.8
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.
- package/CHANGELOG.md +11 -0
- package/HARNESS.md +43 -0
- package/dist/adapters.js +24 -24
- package/dist/benchmarks/_harness.d.ts +1 -1
- package/dist/benchmarks/_harness.js +1 -1
- package/dist/benchmarks/aec-bench.js +2 -2
- package/dist/benchmarks/agentbench.js +2 -2
- package/dist/benchmarks/appworld.js +2 -2
- package/dist/benchmarks/bfcl.js +2 -2
- package/dist/benchmarks/commit0.js +2 -2
- package/dist/benchmarks/crag.js +2 -2
- package/dist/benchmarks/dabstep.js +2 -2
- package/dist/benchmarks/enterpriseops-gym.js +2 -2
- package/dist/benchmarks/finresearchbench.js +2 -2
- package/dist/benchmarks/humaneval.d.ts +10 -1
- package/dist/benchmarks/humaneval.js +5 -3
- package/dist/benchmarks/nomiracl.js +2 -2
- package/dist/benchmarks/open-rag-bench.js +2 -2
- package/dist/benchmarks/programbench.js +2 -2
- package/dist/benchmarks/ragbench.js +2 -2
- package/dist/benchmarks/swe-bench.js +2 -2
- package/dist/benchmarks/t2-ragbench.js +2 -2
- package/dist/benchmarks/tau-bench-shared.js +2 -2
- package/dist/benchmarks/tau2-bench.js +3 -3
- package/dist/benchmarks/tau3-banking.js +3 -3
- package/dist/benchmarks/terminal-bench.js +2 -2
- package/dist/benchmarks/toollm.js +2 -2
- package/dist/benchmarks/webarena-verified.js +2 -2
- package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
- package/dist/{chunk-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
- package/dist/chunk-5H5XV76F.js.map +1 -0
- package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
- package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
- package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
- package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
- package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
- package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
- package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
- package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
- package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
- package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
- package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
- package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
- package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
- package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
- package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
- package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
- package/dist/{chunk-5SBJCB6W.js → chunk-V7AEBY6U.js} +22 -22
- package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
- package/dist/chunk-WSKWVEQB.js.map +1 -0
- package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
- package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
- package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
- package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
- package/dist/index.js +24 -24
- package/package.json +6 -5
- package/scripts/run-package-tests.mjs +30 -8
- package/scripts/verify-packed-consumer.mjs +1 -1
- package/scripts/verify-pier-agent.mts +1 -0
- package/src/benchmarks/_harness.ts +20 -2
- package/src/benchmarks/humaneval.test.mts +122 -0
- package/src/benchmarks/humaneval.ts +100 -27
- package/src/david-attribution.mts +78 -0
- package/src/david-goliath.mts +149 -0
- package/src/hev-improve.mts +25 -6
- package/src/humaneval-object-ablation.mts +201 -0
- package/src/live-improve-campaign-mbpp.mts +641 -0
- package/src/live-improve-campaign.mts +500 -0
- package/src/mbpp-structural.mts +12 -7
- package/src/quant-arena/README.md +144 -0
- package/src/quant-arena/backtest.test.mts +135 -0
- package/src/quant-arena/backtest.ts +218 -0
- package/src/quant-arena/data.test.mts +44 -0
- package/src/quant-arena/data.ts +141 -0
- package/src/quant-arena/driver.test.mts +253 -0
- package/src/quant-arena/driver.ts +219 -0
- package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
- package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
- package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
- package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
- package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
- package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
- package/src/quant-arena/holdout-certify.mts +206 -0
- package/src/quant-arena/holdout-certify.test.mts +82 -0
- package/src/quant-arena/leak-audit.test.mts +79 -0
- package/src/quant-arena/leak-audit.ts +95 -0
- package/src/quant-arena/make-fixtures.mts +161 -0
- package/src/quant-arena/multiplicity.test.mts +68 -0
- package/src/quant-arena/multiplicity.ts +87 -0
- package/src/quant-arena/nautilus-certify.ts +31 -0
- package/src/quant-arena/oms.ts +90 -0
- package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
- package/src/quant-arena/python/pyproject.toml +8 -0
- package/src/quant-arena/python/uv.lock +1297 -0
- package/src/quant-arena/python/vbt-worker.py +192 -0
- package/src/quant-arena/quant-loop.mts +813 -0
- package/src/quant-arena/quant-loop.test.mts +75 -0
- package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
- package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
- package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
- package/src/quant-arena/types.ts +133 -0
- package/src/quant-arena/vbt-client.ts +321 -0
- package/src/quant-arena/vbt-parity.test.mts +183 -0
- package/src/quant-arena/windows.test.mts +45 -0
- package/src/quant-arena/windows.ts +54 -0
- package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
- package/src/rollout-ledger/backfill-swe-arena.test.mts +338 -0
- package/src/rollout-ledger/settle-capture.mts +442 -0
- package/src/rollout-ledger/settle-capture.test.mts +270 -0
- package/src/stream-observe.py +45 -0
- package/src/stream-observe.tpl.html +247 -0
- package/src/supervisor-arena.mts +816 -0
- package/src/swe-arena/activation.mts +228 -0
- package/src/swe-arena/activation.test.mts +303 -0
- package/src/swe-arena/analyze.ts +211 -0
- package/src/swe-arena/arms.ts +804 -0
- package/src/swe-arena/bootstrap-meta.mts +188 -0
- package/src/swe-arena/bootstrap-meta.test.mts +51 -0
- package/src/swe-arena/briefing.mts +217 -0
- package/src/swe-arena/briefing.test.mts +178 -0
- package/src/swe-arena/calibrate.ts +217 -0
- package/src/swe-arena/capabilities.mts +76 -0
- package/src/swe-arena/capabilities.test.mts +57 -0
- package/src/swe-arena/capacity.ts +194 -0
- package/src/swe-arena/cell-evidence.mts +437 -0
- package/src/swe-arena/cell-evidence.test.mts +248 -0
- package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
- package/src/swe-arena/diagnosis-ensemble.ts +520 -0
- package/src/swe-arena/execution.test.mts +1170 -0
- package/src/swe-arena/factory-command-container.ts +284 -0
- package/src/swe-arena/factory-judge-child.mts +228 -0
- package/src/swe-arena/factory.test.mts +643 -0
- package/src/swe-arena/fixtures/analyze.py +80 -0
- package/src/swe-arena/fixtures/excludes.txt +8 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
- package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
- package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
- package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
- package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
- package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
- package/src/swe-arena/fixtures/holdout.json +44 -0
- package/src/swe-arena/fixtures/instances.json +146 -0
- package/src/swe-arena/fixtures/ledger.jsonl +12 -0
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
- package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
- package/src/swe-arena/fixtures/rematch.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
- package/src/swe-arena/fixtures/run-report/README.md +43 -0
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
- package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
- package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
- package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
- package/src/swe-arena/fixtures/worker-tokens.json +42 -0
- package/src/swe-arena/fixtures.ts +237 -0
- package/src/swe-arena/gepa-seat.mts +583 -0
- package/src/swe-arena/gepa-seat.test.mts +635 -0
- package/src/swe-arena/holdout-certify.mts +408 -0
- package/src/swe-arena/holdout-certify.test.mts +160 -0
- package/src/swe-arena/judge-child.mts +37 -0
- package/src/swe-arena/ledger-orphans.mts +77 -0
- package/src/swe-arena/ledger-orphans.test.mts +147 -0
- package/src/swe-arena/lineage-record.mts +164 -0
- package/src/swe-arena/lineage-record.test.mts +115 -0
- package/src/swe-arena/manifest.mts +293 -0
- package/src/swe-arena/manifest.test.mts +169 -0
- package/src/swe-arena/materialize.ts +142 -0
- package/src/swe-arena/outer-loop.mts +2795 -0
- package/src/swe-arena/outer-loop.test.mts +696 -0
- package/src/swe-arena/parity.test.mts +87 -0
- package/src/swe-arena/premeasured-from-cells.mts +281 -0
- package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
- package/src/swe-arena/proc.test.mts +174 -0
- package/src/swe-arena/proc.ts +260 -0
- package/src/swe-arena/profiles/default-author.profile.json +4 -0
- package/src/swe-arena/proposer-fanout.mts +770 -0
- package/src/swe-arena/proposer-fanout.test.mts +619 -0
- package/src/swe-arena/proposer-provenance.mts +177 -0
- package/src/swe-arena/proposer-provenance.test.mts +106 -0
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +183 -0
- package/src/swe-arena/replay.test.mts +300 -0
- package/src/swe-arena/run-experiment.mts +727 -0
- package/src/swe-arena/run-report.mts +75 -0
- package/src/swe-arena/run-supervisor.mjs +297 -0
- package/src/swe-arena/run-supervisor.test.mts +500 -0
- package/src/swe-arena/score-split.mts +140 -0
- package/src/swe-arena/score-split.test.mts +123 -0
- package/src/swe-arena/serialized-judge.ts +414 -0
- package/src/swe-arena/types.ts +218 -0
- package/src/swe-code-improve.mts +328 -0
- package/src/swe-emit-patch.mts +104 -0
- package/src/swe-improve.mts +232 -0
- package/src/swe-jail.ts +2 -2
- package/src/swe-local-proof.mts +169 -0
- package/src/swe-repro-calibrate.mts +446 -0
- package/src/swe-stream.mts +1497 -0
- package/src/swe-structural.mts +245 -837
- package/dist/chunk-LRRD7NAG.js.map +0 -1
- package/dist/chunk-PPYSEKFM.js.map +0 -1
- /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
- /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
- /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
- /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
- /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
- /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
- /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
- /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
- /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
- /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
- /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
- /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
- /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
- /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
- /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
- /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
- /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
- /package/dist/{chunk-5SBJCB6W.js.map → chunk-V7AEBY6U.js.map} +0 -0
- /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
- /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
- /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
- /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""QUANT-ARENA vectorbt scoring worker.
|
|
2
|
+
|
|
3
|
+
Persistent process speaking JSON-lines over stdin/stdout. The TypeScript side
|
|
4
|
+
(vbt-client.ts) spawns ONE worker per campaign and streams score requests to
|
|
5
|
+
it; the worker replies with per-window stats plus the full-range equity curve.
|
|
6
|
+
|
|
7
|
+
Protocol (one JSON object per line):
|
|
8
|
+
request {"id": n, "op": "ping"}
|
|
9
|
+
reply {"id": n, "ok": true, "vbt": "<version>"}
|
|
10
|
+
|
|
11
|
+
request {"id": n, "op": "score",
|
|
12
|
+
"open_prices": [[T x N floats]], # fill prices (next-open model)
|
|
13
|
+
"close_prices": [[T x N floats]], # valuation prices (close-marked equity)
|
|
14
|
+
"weights": [[N floats] | null, ... T rows],
|
|
15
|
+
# decision at day t; null = no rebalance
|
|
16
|
+
# (JSON has no NaN — null plays that role)
|
|
17
|
+
"cost_bps": 10, "slippage_bps": 5,
|
|
18
|
+
"windows": [[start, end], ...]} # [start, end) day-index ranges
|
|
19
|
+
reply {"id": n, "ok": true,
|
|
20
|
+
"windows": [{"totalReturn", "maxDD", "sharpe", "trades"}, ...],
|
|
21
|
+
"full": {same shape, over [0, T)},
|
|
22
|
+
"equity": [T floats]} # group value, close-marked
|
|
23
|
+
errors {"id": n, "ok": false, "error": "<message>"}
|
|
24
|
+
|
|
25
|
+
Execution model (aligned with the TS reference engine, backtest.ts):
|
|
26
|
+
- a decision at the close of day t fills at day t+1's open: the worker
|
|
27
|
+
shifts the weights matrix down one row before handing it to vectorbt;
|
|
28
|
+
- Portfolio.from_orders(size_type='targetpercent', group_by=True,
|
|
29
|
+
cash_sharing=True, call_seq='auto'), fill price = open, valuation
|
|
30
|
+
(val_price for sizing) = open — matching the TS engine's "target dollars
|
|
31
|
+
are a fraction of equity marked at the fill's open";
|
|
32
|
+
- the TS engine charges slippage as a bps FEE on traded dollars, so the
|
|
33
|
+
worker folds slippage_bps into vectorbt's `fees` and passes slippage=0
|
|
34
|
+
(vectorbt's own slippage perturbs the fill price — a different model);
|
|
35
|
+
- stats replicate backtest.ts statsForRange exactly: sample-std (ddof=1)
|
|
36
|
+
annualized Sharpe at 252 days, positive-convention max drawdown with the
|
|
37
|
+
peak reset at each window start, trades counted on fill days inside
|
|
38
|
+
(start, end).
|
|
39
|
+
|
|
40
|
+
Determinism: NUMBA_NUM_THREADS=1 is pinned before import so JIT-compiled
|
|
41
|
+
reductions run single-threaded; a warmup call at startup absorbs the numba
|
|
42
|
+
cold-JIT cost before the first real request.
|
|
43
|
+
|
|
44
|
+
License note: vectorbt is Apache-2.0 + Commons Clause. This worker is for
|
|
45
|
+
internal research use (scoring candidate strategies in the lab); it is not
|
|
46
|
+
sold or offered as a commercial service.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from __future__ import annotations
|
|
50
|
+
|
|
51
|
+
import json
|
|
52
|
+
import math
|
|
53
|
+
import os
|
|
54
|
+
import sys
|
|
55
|
+
|
|
56
|
+
os.environ.setdefault("NUMBA_NUM_THREADS", "1")
|
|
57
|
+
|
|
58
|
+
import numpy as np # noqa: E402
|
|
59
|
+
import pandas as pd # noqa: E402
|
|
60
|
+
import vectorbt as vbt # noqa: E402
|
|
61
|
+
|
|
62
|
+
TRADING_DAYS_PER_YEAR = 252
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def range_stats(equity: np.ndarray, order_days: np.ndarray, start: int, end: int) -> dict:
|
|
66
|
+
"""Mirror of backtest.ts statsForRange over a value curve."""
|
|
67
|
+
if start < 0 or end > equity.shape[0] or end - start < 2:
|
|
68
|
+
raise ValueError(f"bad range [{start}, {end}) over {equity.shape[0]} days")
|
|
69
|
+
eq = equity[start:end]
|
|
70
|
+
total_return = float(eq[-1] / eq[0] - 1.0)
|
|
71
|
+
peak = -math.inf
|
|
72
|
+
max_dd = 0.0
|
|
73
|
+
for value in eq:
|
|
74
|
+
if value > peak:
|
|
75
|
+
peak = value
|
|
76
|
+
dd = (peak - value) / peak
|
|
77
|
+
if dd > max_dd:
|
|
78
|
+
max_dd = dd
|
|
79
|
+
returns = eq[1:] / eq[:-1] - 1.0
|
|
80
|
+
n = returns.shape[0]
|
|
81
|
+
mean = float(returns.mean())
|
|
82
|
+
if n > 1:
|
|
83
|
+
std = float(returns.std(ddof=1))
|
|
84
|
+
else:
|
|
85
|
+
std = 0.0
|
|
86
|
+
sharpe = (mean / std) * math.sqrt(TRADING_DAYS_PER_YEAR) if std > 0 else 0.0
|
|
87
|
+
trades = int(((order_days > start) & (order_days < end)).sum())
|
|
88
|
+
return {
|
|
89
|
+
"totalReturn": total_return,
|
|
90
|
+
"maxDD": float(max_dd),
|
|
91
|
+
"sharpe": float(sharpe),
|
|
92
|
+
"trades": trades,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def run_portfolio(req: dict) -> tuple[np.ndarray, np.ndarray]:
|
|
97
|
+
"""Returns (equity curve close-marked, order fill-day indices)."""
|
|
98
|
+
opens = np.asarray(req["open_prices"], dtype=np.float64)
|
|
99
|
+
closes = np.asarray(req["close_prices"], dtype=np.float64)
|
|
100
|
+
if opens.shape != closes.shape or opens.ndim != 2:
|
|
101
|
+
raise ValueError(f"price shape mismatch: open {opens.shape} vs close {closes.shape}")
|
|
102
|
+
T, N = opens.shape
|
|
103
|
+
weights_rows = req["weights"]
|
|
104
|
+
if len(weights_rows) != T:
|
|
105
|
+
raise ValueError(f"weights has {len(weights_rows)} rows for {T} days")
|
|
106
|
+
|
|
107
|
+
# Decision at close of t -> order row t+1, filled at t+1's open.
|
|
108
|
+
size = np.full((T, N), np.nan)
|
|
109
|
+
for t, row in enumerate(weights_rows):
|
|
110
|
+
if row is None:
|
|
111
|
+
continue
|
|
112
|
+
if t + 1 >= T:
|
|
113
|
+
continue # a decision on the final bar can never fill
|
|
114
|
+
if len(row) != N:
|
|
115
|
+
raise ValueError(f"decision at t={t} has {len(row)} weights for {N} assets")
|
|
116
|
+
size[t + 1] = row
|
|
117
|
+
|
|
118
|
+
fee_rate = (float(req["cost_bps"]) + float(req["slippage_bps"])) / 10_000.0
|
|
119
|
+
columns = [f"A{k}" for k in range(N)]
|
|
120
|
+
close_df = pd.DataFrame(closes, columns=columns)
|
|
121
|
+
open_df = pd.DataFrame(opens, columns=columns)
|
|
122
|
+
size_df = pd.DataFrame(size, columns=columns)
|
|
123
|
+
|
|
124
|
+
pf = vbt.Portfolio.from_orders(
|
|
125
|
+
close=close_df,
|
|
126
|
+
size=size_df,
|
|
127
|
+
price=open_df,
|
|
128
|
+
val_price=open_df,
|
|
129
|
+
size_type="targetpercent",
|
|
130
|
+
group_by=True,
|
|
131
|
+
cash_sharing=True,
|
|
132
|
+
call_seq="auto",
|
|
133
|
+
fees=fee_rate,
|
|
134
|
+
slippage=0.0,
|
|
135
|
+
init_cash=1.0,
|
|
136
|
+
freq="1D",
|
|
137
|
+
)
|
|
138
|
+
equity = np.asarray(pf.value().values, dtype=np.float64)
|
|
139
|
+
order_days = np.asarray(pf.orders.records["idx"], dtype=np.int64)
|
|
140
|
+
return equity, order_days
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def score(req: dict) -> dict:
|
|
144
|
+
equity, order_days = run_portfolio(req)
|
|
145
|
+
T = equity.shape[0]
|
|
146
|
+
windows = [range_stats(equity, order_days, int(w[0]), int(w[1])) for w in req["windows"]]
|
|
147
|
+
return {
|
|
148
|
+
"windows": windows,
|
|
149
|
+
"full": range_stats(equity, order_days, 0, T),
|
|
150
|
+
"equity": [float(v) for v in equity],
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def warmup() -> None:
|
|
155
|
+
req = {
|
|
156
|
+
"open_prices": [[100.0, 50.0]] * 8,
|
|
157
|
+
"close_prices": [[101.0, 49.5]] * 8,
|
|
158
|
+
"weights": [None, [0.5, 0.4], None, None, [0.2, 0.1], None, None, None],
|
|
159
|
+
"cost_bps": 10,
|
|
160
|
+
"slippage_bps": 5,
|
|
161
|
+
"windows": [[0, 8]],
|
|
162
|
+
}
|
|
163
|
+
score(req)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main() -> None:
|
|
167
|
+
warmup()
|
|
168
|
+
sys.stdout.write(json.dumps({"ready": True, "vbt": vbt.__version__}) + "\n")
|
|
169
|
+
sys.stdout.flush()
|
|
170
|
+
for line in sys.stdin:
|
|
171
|
+
line = line.strip()
|
|
172
|
+
if not line:
|
|
173
|
+
continue
|
|
174
|
+
rid = None
|
|
175
|
+
try:
|
|
176
|
+
req = json.loads(line)
|
|
177
|
+
rid = req.get("id")
|
|
178
|
+
op = req.get("op")
|
|
179
|
+
if op == "ping":
|
|
180
|
+
reply = {"id": rid, "ok": True, "vbt": vbt.__version__}
|
|
181
|
+
elif op == "score":
|
|
182
|
+
reply = {"id": rid, "ok": True, **score(req)}
|
|
183
|
+
else:
|
|
184
|
+
reply = {"id": rid, "ok": False, "error": f"unknown op {op!r}"}
|
|
185
|
+
except Exception as exc: # fail per-request, keep serving
|
|
186
|
+
reply = {"id": rid, "ok": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
187
|
+
sys.stdout.write(json.dumps(reply) + "\n")
|
|
188
|
+
sys.stdout.flush()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
main()
|