opencode-pine2pyne 0.1.1__tar.gz → 0.2.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.
Files changed (125) hide show
  1. opencode_pine2pyne-0.2.0/PKG-INFO +301 -0
  2. opencode_pine2pyne-0.2.0/README.md +281 -0
  3. opencode_pine2pyne-0.2.0/opencode_pine2pyne.egg-info/PKG-INFO +301 -0
  4. opencode_pine2pyne-0.2.0/opencode_pine2pyne.egg-info/SOURCES.txt +116 -0
  5. opencode_pine2pyne-0.2.0/opencode_pine2pyne.egg-info/requires.txt +3 -0
  6. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/TRANSPILER_USAGE.md +3 -3
  7. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/__init__.py +11 -7
  8. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/ast_nodes.py +31 -3
  9. opencode_pine2pyne-0.2.0/pine2pyne/ast_walk.py +93 -0
  10. opencode_pine2pyne-0.2.0/pine2pyne/block_value_lowering.py +683 -0
  11. opencode_pine2pyne-0.2.0/pine2pyne/bool_builtins.py +42 -0
  12. opencode_pine2pyne-0.2.0/pine2pyne/bool_value_typing.py +467 -0
  13. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/cli.py +36 -12
  14. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/codegen.py +160 -159
  15. opencode_pine2pyne-0.2.0/pine2pyne/emitted_names.py +138 -0
  16. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/errors.py +20 -0
  17. opencode_pine2pyne-0.2.0/pine2pyne/for_loop_end_bound.py +128 -0
  18. opencode_pine2pyne-0.2.0/pine2pyne/grouped_history_lowering.py +310 -0
  19. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/import_resolver.py +89 -96
  20. opencode_pine2pyne-0.2.0/pine2pyne/library_collision.py +436 -0
  21. opencode_pine2pyne-0.2.0/pine2pyne/namespace_members.py +247 -0
  22. opencode_pine2pyne-0.2.0/pine2pyne/once_lowering.py +75 -0
  23. opencode_pine2pyne-0.2.0/pine2pyne/overload_resolution.py +480 -0
  24. opencode_pine2pyne-0.2.0/pine2pyne/parse_repair.py +398 -0
  25. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/parser.py +272 -93
  26. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/pine_builtins.py +88 -211
  27. opencode_pine2pyne-0.2.0/pine2pyne/pine_semantics.py +581 -0
  28. opencode_pine2pyne-0.2.0/pine2pyne/scope_renamer.py +720 -0
  29. opencode_pine2pyne-0.2.0/pine2pyne/strict_boolean.py +101 -0
  30. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/symbol_table.py +13 -3
  31. opencode_pine2pyne-0.2.0/pine2pyne/transformer.py +2166 -0
  32. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/type_inference.py +89 -1
  33. opencode_pine2pyne-0.2.0/pine2pyne/udt_field_defaults.py +142 -0
  34. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pyproject.toml +14 -2
  35. opencode_pine2pyne-0.2.0/tests/test_block_tail_value_runtime.py +1284 -0
  36. opencode_pine2pyne-0.2.0/tests/test_block_value_runtime.py +1083 -0
  37. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_cli.py +1 -1
  38. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_codegen.py +181 -158
  39. opencode_pine2pyne-0.2.0/tests/test_codegen_operator_grouping.py +252 -0
  40. opencode_pine2pyne-0.2.0/tests/test_codegen_refusal.py +206 -0
  41. opencode_pine2pyne-0.2.0/tests/test_collect_namespace_members.py +106 -0
  42. opencode_pine2pyne-0.2.0/tests/test_compare_ground_truth_completeness.py +185 -0
  43. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_corpus_regression.py +31 -1
  44. opencode_pine2pyne-0.2.0/tests/test_dotted_udt_default_runtime.py +114 -0
  45. opencode_pine2pyne-0.2.0/tests/test_drawing_receiver_runtime.py +53 -0
  46. opencode_pine2pyne-0.2.0/tests/test_emitted_name_collision_runtime.py +269 -0
  47. opencode_pine2pyne-0.2.0/tests/test_emitted_name_plan.py +178 -0
  48. opencode_pine2pyne-0.2.0/tests/test_error_demo_claims.py +321 -0
  49. opencode_pine2pyne-0.2.0/tests/test_error_demo_runtime.py +203 -0
  50. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_errors.py +52 -20
  51. opencode_pine2pyne-0.2.0/tests/test_for_in_pairs_runtime.py +264 -0
  52. opencode_pine2pyne-0.2.0/tests/test_for_loop_dynamic_end_runtime.py +201 -0
  53. opencode_pine2pyne-0.2.0/tests/test_function_tail_runtime.py +603 -0
  54. opencode_pine2pyne-0.2.0/tests/test_generated_tables.py +135 -0
  55. opencode_pine2pyne-0.2.0/tests/test_ground_truth_check_no_output.py +65 -0
  56. opencode_pine2pyne-0.2.0/tests/test_ground_truth_check_own_directory.py +233 -0
  57. opencode_pine2pyne-0.2.0/tests/test_ground_truth_digests.py +144 -0
  58. opencode_pine2pyne-0.2.0/tests/test_ground_truth_manifest_order.py +108 -0
  59. opencode_pine2pyne-0.2.0/tests/test_ground_truth_no_output.py +59 -0
  60. opencode_pine2pyne-0.2.0/tests/test_ground_truth_run_dir_isolation.py +527 -0
  61. opencode_pine2pyne-0.2.0/tests/test_ground_truth_settings.py +86 -0
  62. opencode_pine2pyne-0.2.0/tests/test_ground_truth_wrapper_retired.py +86 -0
  63. opencode_pine2pyne-0.2.0/tests/test_grouped_history_lowering.py +367 -0
  64. opencode_pine2pyne-0.2.0/tests/test_grouped_history_runtime.py +276 -0
  65. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_import_resolver.py +107 -34
  66. opencode_pine2pyne-0.2.0/tests/test_input_constant_and_enum_order_runtime.py +152 -0
  67. opencode_pine2pyne-0.2.0/tests/test_input_history_read_runtime.py +106 -0
  68. opencode_pine2pyne-0.2.0/tests/test_library_name_collision.py +158 -0
  69. opencode_pine2pyne-0.2.0/tests/test_library_name_collision_runtime.py +50 -0
  70. opencode_pine2pyne-0.2.0/tests/test_method_namespaces.py +74 -0
  71. opencode_pine2pyne-0.2.0/tests/test_method_receiver_shape_runtime.py +95 -0
  72. opencode_pine2pyne-0.2.0/tests/test_method_routing_runtime.py +517 -0
  73. opencode_pine2pyne-0.2.0/tests/test_once_structure_parser.py +72 -0
  74. opencode_pine2pyne-0.2.0/tests/test_once_structure_runtime.py +205 -0
  75. opencode_pine2pyne-0.2.0/tests/test_overload_dispatch_runtime.py +403 -0
  76. opencode_pine2pyne-0.2.0/tests/test_overload_resolution.py +324 -0
  77. opencode_pine2pyne-0.2.0/tests/test_parse_repair_card55.py +615 -0
  78. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_parser.py +6 -3
  79. opencode_pine2pyne-0.2.0/tests/test_parser_positions.py +87 -0
  80. opencode_pine2pyne-0.2.0/tests/test_pine_builtins.py +286 -0
  81. opencode_pine2pyne-0.2.0/tests/test_pine_semantics.py +446 -0
  82. opencode_pine2pyne-0.2.0/tests/test_release_artifact_grouping.py +225 -0
  83. opencode_pine2pyne-0.2.0/tests/test_runtime_floor.py +64 -0
  84. opencode_pine2pyne-0.2.0/tests/test_scope_model_drift.py +122 -0
  85. opencode_pine2pyne-0.2.0/tests/test_scope_renamer.py +205 -0
  86. opencode_pine2pyne-0.2.0/tests/test_script_declaration_binding.py +342 -0
  87. opencode_pine2pyne-0.2.0/tests/test_shadowing_runtime.py +227 -0
  88. opencode_pine2pyne-0.2.0/tests/test_strict_and_or_runtime.py +326 -0
  89. opencode_pine2pyne-0.2.0/tests/test_suite_harness_exit_verdict.py +166 -0
  90. opencode_pine2pyne-0.2.0/tests/test_suite_report_compare.py +231 -0
  91. opencode_pine2pyne-0.2.0/tests/test_switch_subject_runtime.py +212 -0
  92. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_symbol_table.py +57 -26
  93. opencode_pine2pyne-0.2.0/tests/test_ta_conditional_runtime.py +310 -0
  94. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_transformer.py +229 -182
  95. opencode_pine2pyne-0.2.0/tests/test_transformer_receiver_typing.py +228 -0
  96. opencode_pine2pyne-0.2.0/tests/test_transformer_single_use.py +49 -0
  97. opencode_pine2pyne-0.2.0/tests/test_udt_array_new_runtime.py +103 -0
  98. opencode_pine2pyne-0.2.0/tests/test_udt_bool_field_default_by_version_runtime.py +105 -0
  99. opencode_pine2pyne-0.2.0/tests/test_udt_field_defaults_runtime.py +254 -0
  100. opencode_pine2pyne-0.2.0/tests/test_udt_field_history_runtime.py +94 -0
  101. opencode_pine2pyne-0.2.0/tests/test_udt_type_proof.py +152 -0
  102. opencode_pine2pyne-0.2.0/tests/test_untyped_na_runtime.py +154 -0
  103. opencode_pine2pyne-0.2.0/tests/test_user_declaration_shadows_builtin_runtime.py +416 -0
  104. opencode_pine2pyne-0.2.0/tests/test_v5_bool_cast_runtime.py +100 -0
  105. opencode_pine2pyne-0.2.0/tests/test_var_history_runtime.py +463 -0
  106. opencode_pine2pyne-0.2.0/tests/test_variable_function_runtime.py +118 -0
  107. opencode_pine2pyne-0.1.1/PKG-INFO +0 -219
  108. opencode_pine2pyne-0.1.1/README.md +0 -199
  109. opencode_pine2pyne-0.1.1/opencode_pine2pyne.egg-info/PKG-INFO +0 -219
  110. opencode_pine2pyne-0.1.1/opencode_pine2pyne.egg-info/SOURCES.txt +0 -37
  111. opencode_pine2pyne-0.1.1/opencode_pine2pyne.egg-info/requires.txt +0 -3
  112. opencode_pine2pyne-0.1.1/pine2pyne/transformer.py +0 -2327
  113. opencode_pine2pyne-0.1.1/tests/test_pine_builtins.py +0 -661
  114. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/LICENSE +0 -0
  115. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/opencode_pine2pyne.egg-info/dependency_links.txt +0 -0
  116. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/opencode_pine2pyne.egg-info/entry_points.txt +0 -0
  117. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/opencode_pine2pyne.egg-info/top_level.txt +0 -0
  118. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/README.md +0 -0
  119. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/TRANSPILER_BEST_PRACTICES.md +0 -0
  120. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/__main__.py +0 -0
  121. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/lexer.py +0 -0
  122. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/pine2pyne/tokens.py +0 -0
  123. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/setup.cfg +0 -0
  124. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_lexer.py +0 -0
  125. {opencode_pine2pyne-0.1.1 → opencode_pine2pyne-0.2.0}/tests/test_screener_regression.py +0 -0
@@ -0,0 +1,301 @@
1
+ Metadata-Version: 2.4
2
+ Name: opencode-pine2pyne
3
+ Version: 0.2.0
4
+ Summary: Pine Script v6 to PyneCore Python transpiler
5
+ Author: rubycell
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/rubycell/pine2pyne
8
+ Project-URL: Repository, https://github.com/rubycell/pine2pyne
9
+ Keywords: pinescript,tradingview,transpiler,pynecore,backtesting
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Office/Business :: Financial :: Investment
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: runtime
18
+ Requires-Dist: opencode-pyneruntime[cli]>=6.9.1; extra == "runtime"
19
+ Dynamic: license-file
20
+
21
+ # PyneCore - Pine Script to Python Transpiler
22
+
23
+ > **Backlog & known bugs:** [Projects board #10](https://github.com/users/rubycell/projects/10)
24
+ > — open work is in **Backlog** (prioritised P0/P1/P2), shipped work in **Done**.
25
+ > Confirmed unfixed transpiler bugs live there too; several are pinned by strict
26
+ > `xfail` tests (see [Running Tests](#running-tests)).
27
+
28
+ ## Installation (New Computer)
29
+
30
+ ### Fastest: the sandbox-setup skill
31
+
32
+ From the repo root, run the bundled setup script (idempotent — safe to re-run):
33
+
34
+ ```bash
35
+ bash ~/.claude/skills/opencode-pine2pyne-setup/setup.sh # from inside this clone; the skill lives in ~/.claude/skills
36
+ ```
37
+
38
+ It builds `.venv`, installs everything below, detects `PINE_CPU_COUNT` /
39
+ `PINE_MAX_WORKERS`, seeds a demo dataset, and runs a smoke test that ends in
40
+ `smoke test : PASS` / `READY`. In Claude Code you can also invoke it as
41
+ `/sandbox-setup`.
42
+
43
+ ### Manual (equivalent steps)
44
+
45
+ ```bash
46
+ # From the repo root:
47
+ python3 -m venv .venv # 1. virtualenv
48
+ .venv/bin/pip install -e . # 2. this transpiler (editable)
49
+ .venv/bin/pip install 'opencode-pyneruntime[cli]>=6.9.1' # 3. PyneCore runtime + `pyne` CLI
50
+ .venv/bin/pip install optuna numpy # 4. optimizer deps (not pulled in by the runtime)
51
+ source .venv/bin/activate # 5. activate (Linux/macOS)
52
+ # .venv\Scripts\activate # (Windows)
53
+ ```
54
+
55
+ Verify the install:
56
+
57
+ ```bash
58
+ .venv/bin/python -c "import pynecore, pine2pyne, optuna, numpy; print('all ok')"
59
+ ```
60
+
61
+ **Note**: All commands below assume you are at the repo root with the venv activated.
62
+
63
+ ## How to Convert Pine Script to Python
64
+
65
+ ### Single File Conversion
66
+
67
+ ```bash
68
+ python -m pine2pyne path/to/script.pine -o path/to/output.py
69
+ ```
70
+
71
+ ### Batch Conversion
72
+
73
+ ```bash
74
+ python -m pine2pyne "sample/pinescript/*.pine" -o workdir/scripts/
75
+ ```
76
+
77
+ ## Running Tests
78
+
79
+ There are **three** independent test systems. They answer different questions,
80
+ so a green run of one says nothing about the others.
81
+
82
+ | System | Question it answers | Runtime |
83
+ |---|---|---|
84
+ | **1. Unit tests** (`tests/`, pytest) | Are the transpiler's internals correct? | ~5 s |
85
+ | **2. Sample suites** (`tools/test_all_*.py`) | Does every corpus sample transpile and run? | ~35 s |
86
+ | **3. Ground truth** (`tools/*_ground_truth*`) | Does it produce the *same numbers* as before? | minutes |
87
+
88
+ ### 1. Unit tests — pytest
89
+
90
+ 2,000+ tests over `pine2pyne/` (93% line / 86% branch coverage).
91
+
92
+ ```bash
93
+ .venv/bin/python -m pytest tests/ --ignore=tests/test_screener_regression.py -q
94
+
95
+ # with coverage
96
+ .venv/bin/python -m pytest tests/ --ignore=tests/test_screener_regression.py \
97
+ --cov=pine2pyne --cov-report=term-missing
98
+ ```
99
+
100
+ > `--ignore` is required: `tests/test_screener_regression.py` imports a
101
+ > `screener` module from a **sibling repo** that is absent here, and fails at
102
+ > collection — which aborts the whole run. Tracked as a P0 card on
103
+ > [board #10](https://github.com/users/rubycell/projects/10).
104
+
105
+ Some tests are `@pytest.mark.xfail(strict=True)` and pin **confirmed unfixed
106
+ bugs**, each with a card on [board #10](https://github.com/users/rubycell/projects/10).
107
+ Strict means fixing a bug turns its marker into a failure until the marker is
108
+ removed, so nothing regresses silently in either direction.
109
+
110
+ The former P0 — codegen dropping Pine grouping, so `10 - (5 - 3)` became
111
+ `10 - 5 - 3` — is fixed (card #6) and guarded by
112
+ `tests/test_codegen_operator_grouping.py`. See [docs/test_plan.md](docs/test_plan.md) for the full bug inventory and the
113
+ measured coverage/mutation results.
114
+
115
+ ### 2. Sample suites — end-to-end transpile + run
116
+
117
+ Transpiles every `.pine` in the corpus and runs it through `pyne`.
118
+
119
+ ```bash
120
+ python tools/test_all_samples.py # sample/pinescript (~411 files)
121
+ python tools/test_all_sample_pds.py # sample_pds (~328 files)
122
+
123
+ python tools/test_all_samples.py "ex_001*" # filter by pattern
124
+ python tools/test_all_samples.py --timeout 30 # default 15s
125
+ python tools/test_all_samples.py --verbose # stderr on failure
126
+ python tools/test_all_samples.py -j 0 # all CPU cores
127
+ python tools/test_all_samples.py --run-dir /tmp/run1 # private scripts/, output/ and report files (card #70)
128
+ ```
129
+
130
+ - Source: `sample/pinescript/`, `sample_pds/` · Output: `workdir/scripts/`
131
+ - `--run-dir DIR` keeps a run's transpiled scripts (`DIR/scripts/`), pyne outputs (`DIR/output/`, passed to `pyne`
132
+ as explicit `--plot/--strat/--trade` paths) and `test_results*.{json,txt}` reports in `DIR`, so two runs, or a run
133
+ and an old report, cannot read each other's files. Without it nothing changes: the shared `workdir/` paths and
134
+ the repo-root reports. `--retry-failed` with `--run-dir` reads `DIR`'s own report.
135
+ - Data: `workdir/data/demo.ohlcv` · Results: `test_results{,_pds}.{json,txt}`
136
+
137
+ **Per-sample expectations** live in `tools/sample_expectations.toml`:
138
+
139
+ ```toml
140
+ [expds_156_Out_of_bounds_index]
141
+ expect_error = "index_error" # MUST raise; running clean = FAIL
142
+
143
+ [ex_300_ticker_modify]
144
+ requires_feeds = { D = "demo", "2D" = "demo_2D" } # -> --security D=demo …
145
+ ```
146
+
147
+ `expect_error` marks Pine documentation samples that exist to *demonstrate* an
148
+ error — they are reported as `XFAIL` and count as passes. `requires_feeds`
149
+ supplies extra OHLCV feeds for samples calling `request.security()` at a
150
+ timeframe other than the chart's; pynecore deliberately will not resample the
151
+ chart feed, so the feed must be explicit. Generate one with:
152
+
153
+ ```bash
154
+ cd workdir && pyne data aggregate data/demo.ohlcv -tf 2D
155
+ ```
156
+
157
+ Shared logic for both suites lives in `tools/_suite_common.py` — edit it there,
158
+ not in each harness.
159
+
160
+ ### 3. Ground truth — numeric regression
161
+
162
+ Checks every freshly generated output file (~1,650 across ~550 samples) against
163
+ its recorded digest. This is the only system that catches *silently wrong
164
+ numbers*: output that runs fine but computes something different.
165
+
166
+ ```bash
167
+ python tools/generate_ground_truth.py # (re)build baselines
168
+ python tools/compare_ground_truth.py # check current output vs baselines
169
+ python tools/compare_ground_truth.py --suite sample_pds -j 12
170
+ ```
171
+
172
+ **The baseline is `ground_truth/digests.json`** (card #62). It holds a sha256 and a
173
+ line count for each output file, plus the sha256 of the data file. Git tracks
174
+ only it and `ground_truth/manifest.json` (under 1 MB together). The output
175
+ CSVs themselves (~920 MB) are gitignored: the generator still copies them to
176
+ `ground_truth/{sample,sample_pds}/` on your machine for diffing. Before writing a
177
+ new baseline, it moves the previous CSVs to `backup/deleteable/ground_truth_csv_<time>/`
178
+ (nothing is deleted; clear old ones out by hand). `manifest.json` lists its samples sorted by
179
+ suite, then filename (card #68), so a one-suite rebuild (`--suite sample`) and a full rebuild on the same data
180
+ give the same samples in the same order, and a rebuild never reorders the tracked file; only `generated_at`
181
+ changes by design.
182
+
183
+ - **Exit codes.** The compare exits 1 when `digests.json` is missing or empty, and
184
+ 2 with `DATA CHANGED` when the data file is not the one the baseline was made
185
+ on (restore it, see *Test data* below, or rebaseline).
186
+ - **Every sample runs in its own directory** (card #70): the generator and the compare make one fresh run
187
+ root, `backup/deleteable/ground_truth_runs/<tool>-<time>/` (or an empty `--run-dir DIR`; a directory that already
188
+ holds files is refused), with `<suite>/<sample>/<data file>/` inside it holding the script, its 10B settings and the three
189
+ CSVs, and collect only from there. An earlier run's file of the same name cannot be read as this run's; a run
190
+ that exits 0 and writes nothing is `no_output` (a failure), never `ok` or `NEW PASS`. Run roots are never
191
+ deleted (see the paragraph below on where they go).
192
+ - **Where run roots go, and the disk they use.** The default run root of the generator, the compare and the
193
+ checker (`ground_truth_check.py capture` and `compare` each make one) is
194
+ `backup/deleteable/ground_truth_runs/<tool>-<time>/`, next to the other disposable output, and each run leaves
195
+ one tree of roughly 0.9 GB there (script, settings and three CSVs per sample). A generate run also copies the
196
+ collected files into `ground_truth/<suite>/` (gitignored). Nothing here is deleted by a tool: when you want the
197
+ space back, move old run roots (and the `gt_compare_<time>/` scratch copies) to the USB backup disk, never `rm`
198
+ them. An explicit `--run-dir DIR` is used as given (it must be empty).
199
+ - **A `REGRESSION` line** names the sample and file whose digest changed. To see
200
+ *what* changed, diff that sample's CSV in the run root's `<suite>/<sample>/<data file>/`
201
+ against a baseline copy. That copy is in `ground_truth/<suite>/<sample>/` if you
202
+ generated on the old commit, or, after a later generate, in the newest
203
+ `backup/deleteable/ground_truth_csv_<time>/`.
204
+ - **`--suite sample` or `--suite sample_pds`** rebuilds one suite and keeps the
205
+ other suite's digests and manifest entries. It refuses (exit 1) unless a
206
+ baseline made on the same data already exists.
207
+ - **Expected noise:** 11 `sample_pds` samples call unseeded `math.random()` and
208
+ differ on every run (expds_022, 037, 039, 043, 045, 081, 091, 092, 104, 114,
209
+ 115).
210
+
211
+ The old end-to-end shell wrapper is retired (card #66): it read `ground_truth/samples/`,
212
+ `ground_truth/samples_pds/` and `ground_truth/strategies/`, which no tool writes, so its
213
+ `--quick` mode passed with zero checks. Its default and `--samples` modes are now
214
+ `python tools/compare_ground_truth.py` (`--suite sample`, `sample_pds` or `both`; it reads
215
+ `workdir/data/VN30F1M_15m.ohlcv` by default, see *Test data*); its `--quick` and
216
+ `--strategies` modes are `python tools/ground_truth_check.py capture`, then `compare`.
217
+
218
+ `tools/ground_truth_check.py` (`capture` / `compare`) is a separate, local check of the
219
+ bigtest strategies and the sample suites. It writes only to `ground_truth_check/` at the
220
+ repo root (gitignored), never to `ground_truth/`, so it cannot change the tracked baseline
221
+ above. `capture --force` moves the old `ground_truth_check/` to
222
+ `backup/deleteable/ground_truth_check_<time>/` (nothing is deleted) and touches nothing else. Both actions run the
223
+ bigtest strategies and the two suite harnesses (`--run-dir`) in one fresh run root under
224
+ `backup/deleteable/ground_truth_runs/` (`--run-dir DIR` names an empty one), read only that run's files, and fail the capture when a suite wrote no report
225
+ or a bigtest file did not transpile. `compare` writes its
226
+ scratch copy of the current outputs to `backup/deleteable/gt_compare_<time>/` and never deletes it (card #67); each
227
+ run leaves one such folder, which you move to the backup disk when you want the space back. A capture made
228
+ before card #63 sits in `ground_truth/` (`transpiled/`, `strategy_runs/`, `test_suite_*`);
229
+ `compare` no longer reads it and says so. Run `capture` again.
230
+
231
+ > Regenerate baselines only when you have *confirmed* the new output is correct
232
+ > — otherwise a bug gets frozen in as the expected result.
233
+
234
+ Both tools run every sample on `workdir/data/VN30F1M_15m.ohlcv`, with a
235
+ `<script>.toml` that sets `initial_capital` to 10,000,000,000, and with one
236
+ per-sample timeout (120 s). Both values live in `tools/_ground_truth_common.py`.
237
+ At pynecore's default capital of 1,000,000, one VN30F1M contract costs more than
238
+ equity, so nearly every strategy would record zero trades (card #57).
239
+
240
+ ### Test data
241
+
242
+ `workdir/data/` is gitignored, so a fresh clone has none and the sample suites
243
+ abort with `Data file not found`. Restore the exact dataset the recorded results
244
+ were measured against:
245
+
246
+ ```bash
247
+ mkdir -p workdir/data
248
+ # Sample suites: CCXT BTCUSDT 1D, plus the 2D/5D feeds `requires_feeds` names
249
+ git show 227f3d2^:workdir/data/demo.ohlcv > workdir/data/demo.ohlcv
250
+ cp tests/fixtures/data/demo.toml workdir/data/demo.toml
251
+ (cd workdir && pyne data aggregate data/demo.ohlcv -tf 2D && pyne data aggregate data/demo.ohlcv -tf 5D)
252
+ # Ground truth: VN30F1M 15m
253
+ git show 5d63d21:workdir/data/VN30F1M_15m.ohlcv > workdir/data/VN30F1M_15m.ohlcv
254
+ cp tests/fixtures/data/VN30F1M_15m.toml workdir/data/VN30F1M_15m.toml
255
+ ```
256
+
257
+ The `.ohlcv` files come from those commits (sha256 `67e827cc1a5f71ed…` for
258
+ demo, `e9ef984b09eaa6d2…` for VN30F1M). Take the `.toml` files from
259
+ `tests/fixtures/data/`, not from the commits. The committed ones number weekdays
260
+ 1..7, and pynecore rejects day 7 (it numbers 0 = Monday … 6 = Sunday). They also
261
+ declare one session per week, which leaves session-anchored values such as
262
+ `ta.vwap` NaN or weekly (card #57).
263
+
264
+ (`/sandbox-setup` instead seeds a small *synthetic* fixture — fine for a smoke
265
+ test, but results will not match the recorded rates.)
266
+
267
+ ## For Claude Code / LLM Development
268
+
269
+ See `CLAUDE.md` in this directory for transpiler architecture, output format specs, optimizer documentation, and CSV rounding rules. See `pine2pyne/README.md` for transpiler internals and transformation rules.
270
+
271
+ ## Optimizing Strategies
272
+
273
+ Quick start: `pyne optimize script.py data.ohlcv params.json -n 20`
274
+
275
+ See [Optimizer.md](./Optimizer.md) for full documentation (parameter JSON format, parallel execution, output files).
276
+
277
+ ### Distributed Optimization (multi-machine)
278
+
279
+ Two tools distribute `pyne optimize` across SSH clusters:
280
+
281
+ | Tool | Model | Best for |
282
+ |------|-------|----------|
283
+ | `pyne-dynamic.sh` | Flat-queue (on-demand dispatch) | Multi-variant runs, heterogeneous clusters, long jobs |
284
+ | `pyne-parallel.sh` | Static pre-assignment | Quick jobs on similar-speed machines |
285
+
286
+ ```bash
287
+ # Dynamic (recommended): flat-queue, pre-syncs once, auto-adapts to machine speed
288
+ # Run from workdir/ directory:
289
+ ../tools/pyne-dynamic.sh scripts/strategy.py data/data.ohlcv optimize_variants/ \
290
+ -H ../tools/machines.txt -C 24 --name my_run --output-dir runs/output/
291
+
292
+ # Static: pre-assigns chunks by core count
293
+ ./tools/pyne-parallel.sh scripts/strategy.py data/data.ohlcv optimize.json \
294
+ -H tools/machines.txt --sync
295
+
296
+ # Check progress / collect results
297
+ ../tools/pyne-dynamic.sh --status
298
+ ../tools/pyne-dynamic.sh --collect
299
+ ```
300
+
301
+ Both use the same `machines.txt` format. See `CLAUDE.md` for cluster setup, machine file format, and troubleshooting.
@@ -0,0 +1,281 @@
1
+ # PyneCore - Pine Script to Python Transpiler
2
+
3
+ > **Backlog & known bugs:** [Projects board #10](https://github.com/users/rubycell/projects/10)
4
+ > — open work is in **Backlog** (prioritised P0/P1/P2), shipped work in **Done**.
5
+ > Confirmed unfixed transpiler bugs live there too; several are pinned by strict
6
+ > `xfail` tests (see [Running Tests](#running-tests)).
7
+
8
+ ## Installation (New Computer)
9
+
10
+ ### Fastest: the sandbox-setup skill
11
+
12
+ From the repo root, run the bundled setup script (idempotent — safe to re-run):
13
+
14
+ ```bash
15
+ bash ~/.claude/skills/opencode-pine2pyne-setup/setup.sh # from inside this clone; the skill lives in ~/.claude/skills
16
+ ```
17
+
18
+ It builds `.venv`, installs everything below, detects `PINE_CPU_COUNT` /
19
+ `PINE_MAX_WORKERS`, seeds a demo dataset, and runs a smoke test that ends in
20
+ `smoke test : PASS` / `READY`. In Claude Code you can also invoke it as
21
+ `/sandbox-setup`.
22
+
23
+ ### Manual (equivalent steps)
24
+
25
+ ```bash
26
+ # From the repo root:
27
+ python3 -m venv .venv # 1. virtualenv
28
+ .venv/bin/pip install -e . # 2. this transpiler (editable)
29
+ .venv/bin/pip install 'opencode-pyneruntime[cli]>=6.9.1' # 3. PyneCore runtime + `pyne` CLI
30
+ .venv/bin/pip install optuna numpy # 4. optimizer deps (not pulled in by the runtime)
31
+ source .venv/bin/activate # 5. activate (Linux/macOS)
32
+ # .venv\Scripts\activate # (Windows)
33
+ ```
34
+
35
+ Verify the install:
36
+
37
+ ```bash
38
+ .venv/bin/python -c "import pynecore, pine2pyne, optuna, numpy; print('all ok')"
39
+ ```
40
+
41
+ **Note**: All commands below assume you are at the repo root with the venv activated.
42
+
43
+ ## How to Convert Pine Script to Python
44
+
45
+ ### Single File Conversion
46
+
47
+ ```bash
48
+ python -m pine2pyne path/to/script.pine -o path/to/output.py
49
+ ```
50
+
51
+ ### Batch Conversion
52
+
53
+ ```bash
54
+ python -m pine2pyne "sample/pinescript/*.pine" -o workdir/scripts/
55
+ ```
56
+
57
+ ## Running Tests
58
+
59
+ There are **three** independent test systems. They answer different questions,
60
+ so a green run of one says nothing about the others.
61
+
62
+ | System | Question it answers | Runtime |
63
+ |---|---|---|
64
+ | **1. Unit tests** (`tests/`, pytest) | Are the transpiler's internals correct? | ~5 s |
65
+ | **2. Sample suites** (`tools/test_all_*.py`) | Does every corpus sample transpile and run? | ~35 s |
66
+ | **3. Ground truth** (`tools/*_ground_truth*`) | Does it produce the *same numbers* as before? | minutes |
67
+
68
+ ### 1. Unit tests — pytest
69
+
70
+ 2,000+ tests over `pine2pyne/` (93% line / 86% branch coverage).
71
+
72
+ ```bash
73
+ .venv/bin/python -m pytest tests/ --ignore=tests/test_screener_regression.py -q
74
+
75
+ # with coverage
76
+ .venv/bin/python -m pytest tests/ --ignore=tests/test_screener_regression.py \
77
+ --cov=pine2pyne --cov-report=term-missing
78
+ ```
79
+
80
+ > `--ignore` is required: `tests/test_screener_regression.py` imports a
81
+ > `screener` module from a **sibling repo** that is absent here, and fails at
82
+ > collection — which aborts the whole run. Tracked as a P0 card on
83
+ > [board #10](https://github.com/users/rubycell/projects/10).
84
+
85
+ Some tests are `@pytest.mark.xfail(strict=True)` and pin **confirmed unfixed
86
+ bugs**, each with a card on [board #10](https://github.com/users/rubycell/projects/10).
87
+ Strict means fixing a bug turns its marker into a failure until the marker is
88
+ removed, so nothing regresses silently in either direction.
89
+
90
+ The former P0 — codegen dropping Pine grouping, so `10 - (5 - 3)` became
91
+ `10 - 5 - 3` — is fixed (card #6) and guarded by
92
+ `tests/test_codegen_operator_grouping.py`. See [docs/test_plan.md](docs/test_plan.md) for the full bug inventory and the
93
+ measured coverage/mutation results.
94
+
95
+ ### 2. Sample suites — end-to-end transpile + run
96
+
97
+ Transpiles every `.pine` in the corpus and runs it through `pyne`.
98
+
99
+ ```bash
100
+ python tools/test_all_samples.py # sample/pinescript (~411 files)
101
+ python tools/test_all_sample_pds.py # sample_pds (~328 files)
102
+
103
+ python tools/test_all_samples.py "ex_001*" # filter by pattern
104
+ python tools/test_all_samples.py --timeout 30 # default 15s
105
+ python tools/test_all_samples.py --verbose # stderr on failure
106
+ python tools/test_all_samples.py -j 0 # all CPU cores
107
+ python tools/test_all_samples.py --run-dir /tmp/run1 # private scripts/, output/ and report files (card #70)
108
+ ```
109
+
110
+ - Source: `sample/pinescript/`, `sample_pds/` · Output: `workdir/scripts/`
111
+ - `--run-dir DIR` keeps a run's transpiled scripts (`DIR/scripts/`), pyne outputs (`DIR/output/`, passed to `pyne`
112
+ as explicit `--plot/--strat/--trade` paths) and `test_results*.{json,txt}` reports in `DIR`, so two runs, or a run
113
+ and an old report, cannot read each other's files. Without it nothing changes: the shared `workdir/` paths and
114
+ the repo-root reports. `--retry-failed` with `--run-dir` reads `DIR`'s own report.
115
+ - Data: `workdir/data/demo.ohlcv` · Results: `test_results{,_pds}.{json,txt}`
116
+
117
+ **Per-sample expectations** live in `tools/sample_expectations.toml`:
118
+
119
+ ```toml
120
+ [expds_156_Out_of_bounds_index]
121
+ expect_error = "index_error" # MUST raise; running clean = FAIL
122
+
123
+ [ex_300_ticker_modify]
124
+ requires_feeds = { D = "demo", "2D" = "demo_2D" } # -> --security D=demo …
125
+ ```
126
+
127
+ `expect_error` marks Pine documentation samples that exist to *demonstrate* an
128
+ error — they are reported as `XFAIL` and count as passes. `requires_feeds`
129
+ supplies extra OHLCV feeds for samples calling `request.security()` at a
130
+ timeframe other than the chart's; pynecore deliberately will not resample the
131
+ chart feed, so the feed must be explicit. Generate one with:
132
+
133
+ ```bash
134
+ cd workdir && pyne data aggregate data/demo.ohlcv -tf 2D
135
+ ```
136
+
137
+ Shared logic for both suites lives in `tools/_suite_common.py` — edit it there,
138
+ not in each harness.
139
+
140
+ ### 3. Ground truth — numeric regression
141
+
142
+ Checks every freshly generated output file (~1,650 across ~550 samples) against
143
+ its recorded digest. This is the only system that catches *silently wrong
144
+ numbers*: output that runs fine but computes something different.
145
+
146
+ ```bash
147
+ python tools/generate_ground_truth.py # (re)build baselines
148
+ python tools/compare_ground_truth.py # check current output vs baselines
149
+ python tools/compare_ground_truth.py --suite sample_pds -j 12
150
+ ```
151
+
152
+ **The baseline is `ground_truth/digests.json`** (card #62). It holds a sha256 and a
153
+ line count for each output file, plus the sha256 of the data file. Git tracks
154
+ only it and `ground_truth/manifest.json` (under 1 MB together). The output
155
+ CSVs themselves (~920 MB) are gitignored: the generator still copies them to
156
+ `ground_truth/{sample,sample_pds}/` on your machine for diffing. Before writing a
157
+ new baseline, it moves the previous CSVs to `backup/deleteable/ground_truth_csv_<time>/`
158
+ (nothing is deleted; clear old ones out by hand). `manifest.json` lists its samples sorted by
159
+ suite, then filename (card #68), so a one-suite rebuild (`--suite sample`) and a full rebuild on the same data
160
+ give the same samples in the same order, and a rebuild never reorders the tracked file; only `generated_at`
161
+ changes by design.
162
+
163
+ - **Exit codes.** The compare exits 1 when `digests.json` is missing or empty, and
164
+ 2 with `DATA CHANGED` when the data file is not the one the baseline was made
165
+ on (restore it, see *Test data* below, or rebaseline).
166
+ - **Every sample runs in its own directory** (card #70): the generator and the compare make one fresh run
167
+ root, `backup/deleteable/ground_truth_runs/<tool>-<time>/` (or an empty `--run-dir DIR`; a directory that already
168
+ holds files is refused), with `<suite>/<sample>/<data file>/` inside it holding the script, its 10B settings and the three
169
+ CSVs, and collect only from there. An earlier run's file of the same name cannot be read as this run's; a run
170
+ that exits 0 and writes nothing is `no_output` (a failure), never `ok` or `NEW PASS`. Run roots are never
171
+ deleted (see the paragraph below on where they go).
172
+ - **Where run roots go, and the disk they use.** The default run root of the generator, the compare and the
173
+ checker (`ground_truth_check.py capture` and `compare` each make one) is
174
+ `backup/deleteable/ground_truth_runs/<tool>-<time>/`, next to the other disposable output, and each run leaves
175
+ one tree of roughly 0.9 GB there (script, settings and three CSVs per sample). A generate run also copies the
176
+ collected files into `ground_truth/<suite>/` (gitignored). Nothing here is deleted by a tool: when you want the
177
+ space back, move old run roots (and the `gt_compare_<time>/` scratch copies) to the USB backup disk, never `rm`
178
+ them. An explicit `--run-dir DIR` is used as given (it must be empty).
179
+ - **A `REGRESSION` line** names the sample and file whose digest changed. To see
180
+ *what* changed, diff that sample's CSV in the run root's `<suite>/<sample>/<data file>/`
181
+ against a baseline copy. That copy is in `ground_truth/<suite>/<sample>/` if you
182
+ generated on the old commit, or, after a later generate, in the newest
183
+ `backup/deleteable/ground_truth_csv_<time>/`.
184
+ - **`--suite sample` or `--suite sample_pds`** rebuilds one suite and keeps the
185
+ other suite's digests and manifest entries. It refuses (exit 1) unless a
186
+ baseline made on the same data already exists.
187
+ - **Expected noise:** 11 `sample_pds` samples call unseeded `math.random()` and
188
+ differ on every run (expds_022, 037, 039, 043, 045, 081, 091, 092, 104, 114,
189
+ 115).
190
+
191
+ The old end-to-end shell wrapper is retired (card #66): it read `ground_truth/samples/`,
192
+ `ground_truth/samples_pds/` and `ground_truth/strategies/`, which no tool writes, so its
193
+ `--quick` mode passed with zero checks. Its default and `--samples` modes are now
194
+ `python tools/compare_ground_truth.py` (`--suite sample`, `sample_pds` or `both`; it reads
195
+ `workdir/data/VN30F1M_15m.ohlcv` by default, see *Test data*); its `--quick` and
196
+ `--strategies` modes are `python tools/ground_truth_check.py capture`, then `compare`.
197
+
198
+ `tools/ground_truth_check.py` (`capture` / `compare`) is a separate, local check of the
199
+ bigtest strategies and the sample suites. It writes only to `ground_truth_check/` at the
200
+ repo root (gitignored), never to `ground_truth/`, so it cannot change the tracked baseline
201
+ above. `capture --force` moves the old `ground_truth_check/` to
202
+ `backup/deleteable/ground_truth_check_<time>/` (nothing is deleted) and touches nothing else. Both actions run the
203
+ bigtest strategies and the two suite harnesses (`--run-dir`) in one fresh run root under
204
+ `backup/deleteable/ground_truth_runs/` (`--run-dir DIR` names an empty one), read only that run's files, and fail the capture when a suite wrote no report
205
+ or a bigtest file did not transpile. `compare` writes its
206
+ scratch copy of the current outputs to `backup/deleteable/gt_compare_<time>/` and never deletes it (card #67); each
207
+ run leaves one such folder, which you move to the backup disk when you want the space back. A capture made
208
+ before card #63 sits in `ground_truth/` (`transpiled/`, `strategy_runs/`, `test_suite_*`);
209
+ `compare` no longer reads it and says so. Run `capture` again.
210
+
211
+ > Regenerate baselines only when you have *confirmed* the new output is correct
212
+ > — otherwise a bug gets frozen in as the expected result.
213
+
214
+ Both tools run every sample on `workdir/data/VN30F1M_15m.ohlcv`, with a
215
+ `<script>.toml` that sets `initial_capital` to 10,000,000,000, and with one
216
+ per-sample timeout (120 s). Both values live in `tools/_ground_truth_common.py`.
217
+ At pynecore's default capital of 1,000,000, one VN30F1M contract costs more than
218
+ equity, so nearly every strategy would record zero trades (card #57).
219
+
220
+ ### Test data
221
+
222
+ `workdir/data/` is gitignored, so a fresh clone has none and the sample suites
223
+ abort with `Data file not found`. Restore the exact dataset the recorded results
224
+ were measured against:
225
+
226
+ ```bash
227
+ mkdir -p workdir/data
228
+ # Sample suites: CCXT BTCUSDT 1D, plus the 2D/5D feeds `requires_feeds` names
229
+ git show 227f3d2^:workdir/data/demo.ohlcv > workdir/data/demo.ohlcv
230
+ cp tests/fixtures/data/demo.toml workdir/data/demo.toml
231
+ (cd workdir && pyne data aggregate data/demo.ohlcv -tf 2D && pyne data aggregate data/demo.ohlcv -tf 5D)
232
+ # Ground truth: VN30F1M 15m
233
+ git show 5d63d21:workdir/data/VN30F1M_15m.ohlcv > workdir/data/VN30F1M_15m.ohlcv
234
+ cp tests/fixtures/data/VN30F1M_15m.toml workdir/data/VN30F1M_15m.toml
235
+ ```
236
+
237
+ The `.ohlcv` files come from those commits (sha256 `67e827cc1a5f71ed…` for
238
+ demo, `e9ef984b09eaa6d2…` for VN30F1M). Take the `.toml` files from
239
+ `tests/fixtures/data/`, not from the commits. The committed ones number weekdays
240
+ 1..7, and pynecore rejects day 7 (it numbers 0 = Monday … 6 = Sunday). They also
241
+ declare one session per week, which leaves session-anchored values such as
242
+ `ta.vwap` NaN or weekly (card #57).
243
+
244
+ (`/sandbox-setup` instead seeds a small *synthetic* fixture — fine for a smoke
245
+ test, but results will not match the recorded rates.)
246
+
247
+ ## For Claude Code / LLM Development
248
+
249
+ See `CLAUDE.md` in this directory for transpiler architecture, output format specs, optimizer documentation, and CSV rounding rules. See `pine2pyne/README.md` for transpiler internals and transformation rules.
250
+
251
+ ## Optimizing Strategies
252
+
253
+ Quick start: `pyne optimize script.py data.ohlcv params.json -n 20`
254
+
255
+ See [Optimizer.md](./Optimizer.md) for full documentation (parameter JSON format, parallel execution, output files).
256
+
257
+ ### Distributed Optimization (multi-machine)
258
+
259
+ Two tools distribute `pyne optimize` across SSH clusters:
260
+
261
+ | Tool | Model | Best for |
262
+ |------|-------|----------|
263
+ | `pyne-dynamic.sh` | Flat-queue (on-demand dispatch) | Multi-variant runs, heterogeneous clusters, long jobs |
264
+ | `pyne-parallel.sh` | Static pre-assignment | Quick jobs on similar-speed machines |
265
+
266
+ ```bash
267
+ # Dynamic (recommended): flat-queue, pre-syncs once, auto-adapts to machine speed
268
+ # Run from workdir/ directory:
269
+ ../tools/pyne-dynamic.sh scripts/strategy.py data/data.ohlcv optimize_variants/ \
270
+ -H ../tools/machines.txt -C 24 --name my_run --output-dir runs/output/
271
+
272
+ # Static: pre-assigns chunks by core count
273
+ ./tools/pyne-parallel.sh scripts/strategy.py data/data.ohlcv optimize.json \
274
+ -H tools/machines.txt --sync
275
+
276
+ # Check progress / collect results
277
+ ../tools/pyne-dynamic.sh --status
278
+ ../tools/pyne-dynamic.sh --collect
279
+ ```
280
+
281
+ Both use the same `machines.txt` format. See `CLAUDE.md` for cluster setup, machine file format, and troubleshooting.