py-backlight 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.
Files changed (207) hide show
  1. py_backlight-0.1.0/.gitignore +10 -0
  2. py_backlight-0.1.0/PKG-INFO +243 -0
  3. py_backlight-0.1.0/README.md +203 -0
  4. py_backlight-0.1.0/pyproject.toml +92 -0
  5. py_backlight-0.1.0/src/backlight/__init__.py +14 -0
  6. py_backlight-0.1.0/src/backlight/_gui/assets/AccountView-CbigG0KS.js +1 -0
  7. py_backlight-0.1.0/src/backlight/_gui/assets/AccountView-DnAwP8fS.css +1 -0
  8. py_backlight-0.1.0/src/backlight/_gui/assets/BacktestLabView-DRPXydD8.css +1 -0
  9. py_backlight-0.1.0/src/backlight/_gui/assets/BacktestLabView-vvjxJ5m8.js +2 -0
  10. py_backlight-0.1.0/src/backlight/_gui/assets/CampaignView-CIlc_wqc.css +1 -0
  11. py_backlight-0.1.0/src/backlight/_gui/assets/CampaignView-Cdfvmpsd.js +1 -0
  12. py_backlight-0.1.0/src/backlight/_gui/assets/CompareView-6H2jxNiO.css +1 -0
  13. py_backlight-0.1.0/src/backlight/_gui/assets/CompareView-CrJ8oxXP.js +1 -0
  14. py_backlight-0.1.0/src/backlight/_gui/assets/DashboardView-C0Fxmzc8.css +1 -0
  15. py_backlight-0.1.0/src/backlight/_gui/assets/DashboardView-DgqPCo7B.js +1 -0
  16. py_backlight-0.1.0/src/backlight/_gui/assets/EquityChart-BpYxPxyQ.js +1 -0
  17. py_backlight-0.1.0/src/backlight/_gui/assets/EquityChart-JLe9hJb5.css +1 -0
  18. py_backlight-0.1.0/src/backlight/_gui/assets/JobProgress-C8SnTQlc.css +1 -0
  19. py_backlight-0.1.0/src/backlight/_gui/assets/JobProgress-CB1VLleo.js +1 -0
  20. py_backlight-0.1.0/src/backlight/_gui/assets/LiveView-BVCPEc4g.js +1 -0
  21. py_backlight-0.1.0/src/backlight/_gui/assets/LiveView-DvTIB5_8.css +1 -0
  22. py_backlight-0.1.0/src/backlight/_gui/assets/ModeledQuotesNotice-D0qjkt2g.js +1 -0
  23. py_backlight-0.1.0/src/backlight/_gui/assets/ModeledQuotesNotice-DCxPJFwb.css +1 -0
  24. py_backlight-0.1.0/src/backlight/_gui/assets/ParamFields-Bv3NSsrU.css +1 -0
  25. py_backlight-0.1.0/src/backlight/_gui/assets/ParamFields-DuoR6hfm.js +1 -0
  26. py_backlight-0.1.0/src/backlight/_gui/assets/PendingWork-CeFaVq-R.js +1 -0
  27. py_backlight-0.1.0/src/backlight/_gui/assets/PendingWork-DvsBtxZp.css +1 -0
  28. py_backlight-0.1.0/src/backlight/_gui/assets/PipelinesView-DfsIL-LQ.js +16 -0
  29. py_backlight-0.1.0/src/backlight/_gui/assets/PipelinesView-uM0b2NEl.css +1 -0
  30. py_backlight-0.1.0/src/backlight/_gui/assets/RunDetailView-Bo1vVl8s.css +1 -0
  31. py_backlight-0.1.0/src/backlight/_gui/assets/RunDetailView-CHmNsBqj.js +3 -0
  32. py_backlight-0.1.0/src/backlight/_gui/assets/RunsView-CyjlERtU.css +1 -0
  33. py_backlight-0.1.0/src/backlight/_gui/assets/RunsView-DF-h2_0z.js +1 -0
  34. py_backlight-0.1.0/src/backlight/_gui/assets/SettingsView-86qI0Qk1.css +1 -0
  35. py_backlight-0.1.0/src/backlight/_gui/assets/SettingsView-DpvjM-g5.js +4 -0
  36. py_backlight-0.1.0/src/backlight/_gui/assets/SymbolsInput-B0Xwgzk1.css +1 -0
  37. py_backlight-0.1.0/src/backlight/_gui/assets/SymbolsInput-BZQQUVay.js +2 -0
  38. py_backlight-0.1.0/src/backlight/_gui/assets/TagsEditor-BbnU4zcL.css +1 -0
  39. py_backlight-0.1.0/src/backlight/_gui/assets/TagsEditor-aMliL_fI.js +26 -0
  40. py_backlight-0.1.0/src/backlight/_gui/assets/TradeAccountsView-BDXBsKhm.css +1 -0
  41. py_backlight-0.1.0/src/backlight/_gui/assets/TradeAccountsView-C6Dq9TAx.js +1 -0
  42. py_backlight-0.1.0/src/backlight/_gui/assets/TreesView-B1yCydQC.css +1 -0
  43. py_backlight-0.1.0/src/backlight/_gui/assets/TreesView-JCR9VFL1.js +6 -0
  44. py_backlight-0.1.0/src/backlight/_gui/assets/WebKeysPanel-BYAPo1o9.css +1 -0
  45. py_backlight-0.1.0/src/backlight/_gui/assets/WebKeysPanel-C63Vkpqw.js +1 -0
  46. py_backlight-0.1.0/src/backlight/_gui/assets/WorldPicker-CL4AVVEG.js +1 -0
  47. py_backlight-0.1.0/src/backlight/_gui/assets/WorldPicker-CmwvzOtp.css +1 -0
  48. py_backlight-0.1.0/src/backlight/_gui/assets/formMemory-Dnvfj_p0.js +1 -0
  49. py_backlight-0.1.0/src/backlight/_gui/assets/graph-BOsYCB6S.js +1 -0
  50. py_backlight-0.1.0/src/backlight/_gui/assets/highlight-HvVvo4wj.js +4 -0
  51. py_backlight-0.1.0/src/backlight/_gui/assets/index-BfAt6891.js +44 -0
  52. py_backlight-0.1.0/src/backlight/_gui/assets/index-Cs4gchcM.css +5 -0
  53. py_backlight-0.1.0/src/backlight/_gui/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  54. py_backlight-0.1.0/src/backlight/_gui/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  55. py_backlight-0.1.0/src/backlight/_gui/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  56. py_backlight-0.1.0/src/backlight/_gui/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  57. py_backlight-0.1.0/src/backlight/_gui/favicon.svg +1 -0
  58. py_backlight-0.1.0/src/backlight/_gui/guide.html +1433 -0
  59. py_backlight-0.1.0/src/backlight/_gui/index.html +14 -0
  60. py_backlight-0.1.0/src/backlight/agent/__init__.py +21 -0
  61. py_backlight-0.1.0/src/backlight/agent/draft.py +319 -0
  62. py_backlight-0.1.0/src/backlight/agent/llm.py +403 -0
  63. py_backlight-0.1.0/src/backlight/agent/loop.py +159 -0
  64. py_backlight-0.1.0/src/backlight/agent/nodegen.py +571 -0
  65. py_backlight-0.1.0/src/backlight/agent/prompts.py +99 -0
  66. py_backlight-0.1.0/src/backlight/agent/sessions.py +141 -0
  67. py_backlight-0.1.0/src/backlight/agent/settings.py +114 -0
  68. py_backlight-0.1.0/src/backlight/agent/tools.py +305 -0
  69. py_backlight-0.1.0/src/backlight/cli.py +622 -0
  70. py_backlight-0.1.0/src/backlight/connectors/__init__.py +8 -0
  71. py_backlight-0.1.0/src/backlight/connectors/_http.py +305 -0
  72. py_backlight-0.1.0/src/backlight/connectors/alpaca/__init__.py +640 -0
  73. py_backlight-0.1.0/src/backlight/connectors/alpaca/orders.py +197 -0
  74. py_backlight-0.1.0/src/backlight/connectors/alpaca/stream.py +64 -0
  75. py_backlight-0.1.0/src/backlight/connectors/base.py +385 -0
  76. py_backlight-0.1.0/src/backlight/connectors/csv/__init__.py +122 -0
  77. py_backlight-0.1.0/src/backlight/connectors/databento/__init__.py +676 -0
  78. py_backlight-0.1.0/src/backlight/connectors/databento/batch.py +181 -0
  79. py_backlight-0.1.0/src/backlight/connectors/kalshi/__init__.py +95 -0
  80. py_backlight-0.1.0/src/backlight/connectors/massive/__init__.py +576 -0
  81. py_backlight-0.1.0/src/backlight/connectors/massive_files/__init__.py +674 -0
  82. py_backlight-0.1.0/src/backlight/connectors/schwab/__init__.py +658 -0
  83. py_backlight-0.1.0/src/backlight/connectors/schwab/auth.py +193 -0
  84. py_backlight-0.1.0/src/backlight/connectors/schwab/orders.py +261 -0
  85. py_backlight-0.1.0/src/backlight/connectors/sim/__init__.py +9 -0
  86. py_backlight-0.1.0/src/backlight/connectors/sim/engine.py +513 -0
  87. py_backlight-0.1.0/src/backlight/connectors/synthetic/__init__.py +203 -0
  88. py_backlight-0.1.0/src/backlight/connectors/synthetic/market.py +75 -0
  89. py_backlight-0.1.0/src/backlight/connectors/world.py +285 -0
  90. py_backlight-0.1.0/src/backlight/data/__init__.py +1 -0
  91. py_backlight-0.1.0/src/backlight/data/cache.py +210 -0
  92. py_backlight-0.1.0/src/backlight/data/cache_transfer.py +400 -0
  93. py_backlight-0.1.0/src/backlight/data/coverage.py +109 -0
  94. py_backlight-0.1.0/src/backlight/data/option_cache.py +239 -0
  95. py_backlight-0.1.0/src/backlight/data/option_history.py +874 -0
  96. py_backlight-0.1.0/src/backlight/data/options.py +437 -0
  97. py_backlight-0.1.0/src/backlight/data/session.py +187 -0
  98. py_backlight-0.1.0/src/backlight/data/symbol_sets.py +218 -0
  99. py_backlight-0.1.0/src/backlight/data/symbols.py +226 -0
  100. py_backlight-0.1.0/src/backlight/db/__init__.py +42 -0
  101. py_backlight-0.1.0/src/backlight/db/migrations.py +77 -0
  102. py_backlight-0.1.0/src/backlight/db/names.py +83 -0
  103. py_backlight-0.1.0/src/backlight/db/records.py +159 -0
  104. py_backlight-0.1.0/src/backlight/db/reports/__init__.py +1 -0
  105. py_backlight-0.1.0/src/backlight/db/reports/blotter.py +245 -0
  106. py_backlight-0.1.0/src/backlight/db/reports/markdown.py +66 -0
  107. py_backlight-0.1.0/src/backlight/db/reports/metrics.py +213 -0
  108. py_backlight-0.1.0/src/backlight/db/reports/run_report.py +275 -0
  109. py_backlight-0.1.0/src/backlight/db/runs.py +274 -0
  110. py_backlight-0.1.0/src/backlight/db/schema.sql +270 -0
  111. py_backlight-0.1.0/src/backlight/db/scratch.py +65 -0
  112. py_backlight-0.1.0/src/backlight/hub/__init__.py +1 -0
  113. py_backlight-0.1.0/src/backlight/hub/access.py +116 -0
  114. py_backlight-0.1.0/src/backlight/hub/api/__init__.py +129 -0
  115. py_backlight-0.1.0/src/backlight/hub/api/_deps.py +10 -0
  116. py_backlight-0.1.0/src/backlight/hub/api/agent.py +176 -0
  117. py_backlight-0.1.0/src/backlight/hub/api/backtests.py +459 -0
  118. py_backlight-0.1.0/src/backlight/hub/api/campaigns.py +807 -0
  119. py_backlight-0.1.0/src/backlight/hub/api/catalog.py +42 -0
  120. py_backlight-0.1.0/src/backlight/hub/api/connectors.py +149 -0
  121. py_backlight-0.1.0/src/backlight/hub/api/data.py +565 -0
  122. py_backlight-0.1.0/src/backlight/hub/api/desk.py +337 -0
  123. py_backlight-0.1.0/src/backlight/hub/api/live.py +229 -0
  124. py_backlight-0.1.0/src/backlight/hub/api/pipelines.py +155 -0
  125. py_backlight-0.1.0/src/backlight/hub/api/reports.py +33 -0
  126. py_backlight-0.1.0/src/backlight/hub/api/runs.py +272 -0
  127. py_backlight-0.1.0/src/backlight/hub/api/settings.py +88 -0
  128. py_backlight-0.1.0/src/backlight/hub/api/symbol_sets.py +99 -0
  129. py_backlight-0.1.0/src/backlight/hub/api/trees.py +141 -0
  130. py_backlight-0.1.0/src/backlight/hub/api/users.py +301 -0
  131. py_backlight-0.1.0/src/backlight/hub/api/worlds.py +542 -0
  132. py_backlight-0.1.0/src/backlight/hub/app.py +106 -0
  133. py_backlight-0.1.0/src/backlight/hub/auth.py +686 -0
  134. py_backlight-0.1.0/src/backlight/hub/config.py +111 -0
  135. py_backlight-0.1.0/src/backlight/hub/events.py +60 -0
  136. py_backlight-0.1.0/src/backlight/hub/jobs.py +308 -0
  137. py_backlight-0.1.0/src/backlight/hub/login.html +93 -0
  138. py_backlight-0.1.0/src/backlight/hub/reconcile.py +190 -0
  139. py_backlight-0.1.0/src/backlight/hub/secrets.py +84 -0
  140. py_backlight-0.1.0/src/backlight/hub/services.py +125 -0
  141. py_backlight-0.1.0/src/backlight/hub/users.py +281 -0
  142. py_backlight-0.1.0/src/backlight/hub/webkeys.py +88 -0
  143. py_backlight-0.1.0/src/backlight/indicators.py +212 -0
  144. py_backlight-0.1.0/src/backlight/logsetup.py +31 -0
  145. py_backlight-0.1.0/src/backlight/paths.py +127 -0
  146. py_backlight-0.1.0/src/backlight/pipeline/__init__.py +1 -0
  147. py_backlight-0.1.0/src/backlight/pipeline/allocation.py +160 -0
  148. py_backlight-0.1.0/src/backlight/pipeline/context.py +307 -0
  149. py_backlight-0.1.0/src/backlight/pipeline/engine.py +169 -0
  150. py_backlight-0.1.0/src/backlight/pipeline/graph/__init__.py +1 -0
  151. py_backlight-0.1.0/src/backlight/pipeline/graph/catalog.py +71 -0
  152. py_backlight-0.1.0/src/backlight/pipeline/graph/plugins.py +64 -0
  153. py_backlight-0.1.0/src/backlight/pipeline/graph/presets.py +599 -0
  154. py_backlight-0.1.0/src/backlight/pipeline/graph/schema.py +277 -0
  155. py_backlight-0.1.0/src/backlight/pipeline/graph/store.py +118 -0
  156. py_backlight-0.1.0/src/backlight/pipeline/graph/trees.py +116 -0
  157. py_backlight-0.1.0/src/backlight/pipeline/memory.py +231 -0
  158. py_backlight-0.1.0/src/backlight/pipeline/nodes/__init__.py +7 -0
  159. py_backlight-0.1.0/src/backlight/pipeline/nodes/base.py +344 -0
  160. py_backlight-0.1.0/src/backlight/pipeline/nodes/execution.py +112 -0
  161. py_backlight-0.1.0/src/backlight/pipeline/nodes/flow.py +128 -0
  162. py_backlight-0.1.0/src/backlight/pipeline/nodes/option_strategies.py +1112 -0
  163. py_backlight-0.1.0/src/backlight/pipeline/nodes/output.py +119 -0
  164. py_backlight-0.1.0/src/backlight/pipeline/nodes/pickers.py +177 -0
  165. py_backlight-0.1.0/src/backlight/pipeline/nodes/refiners.py +210 -0
  166. py_backlight-0.1.0/src/backlight/pipeline/nodes/selectors.py +150 -0
  167. py_backlight-0.1.0/src/backlight/pipeline/nodes/sizers.py +504 -0
  168. py_backlight-0.1.0/src/backlight/pipeline/nodes/strategies.py +334 -0
  169. py_backlight-0.1.0/src/backlight/pipeline/nodes/triggers.py +84 -0
  170. py_backlight-0.1.0/src/backlight/pipeline/nodes/universe.py +70 -0
  171. py_backlight-0.1.0/src/backlight/pipeline/options.py +495 -0
  172. py_backlight-0.1.0/src/backlight/pipeline/params.py +259 -0
  173. py_backlight-0.1.0/src/backlight/pipeline/picking.py +321 -0
  174. py_backlight-0.1.0/src/backlight/pipeline/ports.py +216 -0
  175. py_backlight-0.1.0/src/backlight/pipeline/tree/__init__.py +7 -0
  176. py_backlight-0.1.0/src/backlight/pipeline/tree/expr.py +323 -0
  177. py_backlight-0.1.0/src/backlight/pipeline/tree/model.py +259 -0
  178. py_backlight-0.1.0/src/backlight/pipeline/tree/templates.py +479 -0
  179. py_backlight-0.1.0/src/backlight/pipeline/warmup.py +145 -0
  180. py_backlight-0.1.0/src/backlight/pipeline/web.py +311 -0
  181. py_backlight-0.1.0/src/backlight/runner/__init__.py +1 -0
  182. py_backlight-0.1.0/src/backlight/runner/account.py +226 -0
  183. py_backlight-0.1.0/src/backlight/runner/advise.py +388 -0
  184. py_backlight-0.1.0/src/backlight/runner/backtest.py +600 -0
  185. py_backlight-0.1.0/src/backlight/runner/campaign.py +1818 -0
  186. py_backlight-0.1.0/src/backlight/runner/desk.py +867 -0
  187. py_backlight-0.1.0/src/backlight/runner/feed.py +516 -0
  188. py_backlight-0.1.0/src/backlight/runner/live.py +884 -0
  189. py_backlight-0.1.0/src/backlight/runner/portfolio.py +314 -0
  190. py_backlight-0.1.0/src/backlight/runner/prefetch.py +105 -0
  191. py_backlight-0.1.0/src/backlight/runner/recorder.py +64 -0
  192. py_backlight-0.1.0/src/backlight/runner/restore.py +183 -0
  193. py_backlight-0.1.0/src/backlight/runner/step.py +102 -0
  194. py_backlight-0.1.0/src/backlight/runner/walk.py +154 -0
  195. py_backlight-0.1.0/src/backlight/types.py +522 -0
  196. py_backlight-0.1.0/src/backlight/version.py +1 -0
  197. py_backlight-0.1.0/src/backlight/worlds/__init__.py +17 -0
  198. py_backlight-0.1.0/src/backlight/worlds/analytics.py +459 -0
  199. py_backlight-0.1.0/src/backlight/worlds/debrief.py +178 -0
  200. py_backlight-0.1.0/src/backlight/worlds/gen_v1.py +424 -0
  201. py_backlight-0.1.0/src/backlight/worlds/personalities.py +270 -0
  202. py_backlight-0.1.0/src/backlight/worlds/rng.py +69 -0
  203. py_backlight-0.1.0/src/backlight/worlds/scenarios.py +106 -0
  204. py_backlight-0.1.0/src/backlight/worlds/sets.py +198 -0
  205. py_backlight-0.1.0/src/backlight/worlds/store.py +683 -0
  206. py_backlight-0.1.0/src/backlight/worlds/surface.py +167 -0
  207. py_backlight-0.1.0/src/backlight/worlds/tags.py +339 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ .venv/
5
+ gui/node_modules/
6
+ gui/dist/
7
+ src/backlight/_gui/
8
+ dist/
9
+ .claude/worktrees/
10
+ *.tmp
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.5
2
+ Name: py-backlight
3
+ Version: 0.1.0
4
+ Summary: Backtest, paper trade and live trade stock and option strategies built from pipelines of nodes
5
+ Project-URL: Homepage, https://gitlab.com/JasonBerger/backlight
6
+ Project-URL: Documentation, https://gitlab.com/JasonBerger/backlight/-/blob/main/README.md
7
+ Project-URL: Repository, https://gitlab.com/JasonBerger/backlight
8
+ Project-URL: Issues, https://gitlab.com/JasonBerger/backlight/-/issues
9
+ Project-URL: Source, https://gitlab.com/JasonBerger/backlight
10
+ Author-email: Jason Berger <berge472@gmail.com>
11
+ License: MIT
12
+ Keywords: algorithmic-trading,backtesting,options,paper-trading,trading
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Topic :: Office/Business :: Financial :: Investment
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: fastapi>=0.110
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: numpy>=1.26
24
+ Requires-Dist: pyarrow>=15
25
+ Requires-Dist: pydantic>=2.5
26
+ Requires-Dist: pywebview>=5.0
27
+ Requires-Dist: pyyaml>=6.0
28
+ Requires-Dist: uvicorn>=0.27
29
+ Requires-Dist: websockets>=12
30
+ Provides-Extra: agent
31
+ Requires-Dist: anthropic>=1.0; extra == 'agent'
32
+ Requires-Dist: openai>=1.40; extra == 'agent'
33
+ Provides-Extra: databento
34
+ Requires-Dist: databento-dbn>=0.20; extra == 'databento'
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest-timeout>=2; extra == 'dev'
37
+ Requires-Dist: pytest>=7; extra == 'dev'
38
+ Requires-Dist: ruff>=0.4; extra == 'dev'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # Backlight
42
+
43
+ Automated stock and options trading platform: pipelines of parameterized nodes
44
+ pick securities, decide how to trade them through tagged decision trees, refine
45
+ those decisions, and hand orders to a connector. One runner drives the same
46
+ pipeline in backtest, paper, or live mode. Built on the same stack as proxer:
47
+ pywebview + FastAPI + Vue 3 (Vuetify).
48
+
49
+ ## Quick start
50
+
51
+ ```sh
52
+ pip install py-backlight # or, from a checkout: make install (pip install -e .[dev] + build the UI)
53
+ backlight hub # opens the desktop window (or: backlight hub --headless)
54
+ ```
55
+
56
+ The PyPI distribution is `py-backlight` (`backlight` there is an old, empty
57
+ project); the package, the import and the command are all `backlight`. Extras:
58
+ `py-backlight[agent]` (the pipeline agent) and `py-backlight[databento]`.
59
+ `make check` builds the wheel with the UI bundled and runs `twine check`;
60
+ `make deploy` uploads it.
61
+
62
+ Upgrading from `trader`, the old name: the first run moves `~/.config/trader`,
63
+ `~/.trader` (and `trader.db` in it) and `~/.cache/trader` to their `backlight`
64
+ names, `TRADER_*` environment variables still work where no `BACKLIGHT_*`
65
+ one is set, and agent nodes are rewritten to import `backlight`.
66
+
67
+ Without the window, the API is at `http://127.0.0.1:8770/docs`.
68
+
69
+ Deployed on a cluster, headless, behind a Cloudflare tunnel and with sign-in
70
+ on: see [docs/deploy.md](docs/deploy.md) (Dockerfile, Helm chart in
71
+ `charts/backlight`). Sign-in is optional — off on the desktop unless you add a
72
+ user with `backlight auth add-user NAME`.
73
+
74
+ From the command line:
75
+
76
+ ```sh
77
+ backlight catalog # node types and connectors
78
+ backlight backtest preset-sma-cross --symbols SPY,QQQ --start 2022-01-01 --end 2023-12-31
79
+ backlight runs --mode backtest
80
+ backlight report <run_id> # markdown run report
81
+ backlight report <run_id_a> <run_id_b> # compare
82
+ backlight purge --older-than-days 30 --vacuum # delete old backtest runs
83
+ backlight fetch --provider massive --symbols SPY --start 2020-01-01 --end 2024-12-31
84
+ backlight flatfiles --start 2024-01-01 --end 2024-12-31 # Massive option minute files, once
85
+ backlight backtest preset-opt-0dte-condor --symbols SPY --provider alpaca --option-provider massive_files
86
+ ```
87
+
88
+ The first launch seeds fifteen preset pipelines (stock, options, and two branching
89
+ examples) and four decision trees into
90
+ `~/.config/backlight/`, and the `synthetic` data connector needs no network, so
91
+ the Backtest Lab works out of the box.
92
+
93
+ ## Layout
94
+
95
+ | Path | What |
96
+ | --- | --- |
97
+ | `src/backlight/types.py` | The objects that cross node ports: `SecurityId`, `Bar`, `Quote`, `OptionContract`, `Universe`, `Signal`, `Intent`, `OrderRequest`, `OrderEvent`, `Position`. |
98
+ | `src/backlight/pipeline/` | `params.py` (parameter schemas, from proxer), `nodes/` (trigger, universe, selector, strategy, refiner, sizer, execution, output, flow), `tree/` (decision tree model, safe expression language, action templates), `graph/` (pipeline docs, validation, compile, catalog, plugins, stores, presets), `engine.py` (the tick). |
99
+ | `src/backlight/connectors/` | `base.py` (the three interfaces), `sim` (fill model, brackets, option expiry), `synthetic` (offline random-walk bars, Black-Scholes chains, paper-mode market data), `csv`, `schwab`, `alpaca`, `massive`, `massive_files` (option flat files over S3), `kalshi` (plan only). |
100
+ | `src/backlight/runner/` | `portfolio.py` (ledger), `feed.py` (bar views for nodes and the sim), `backtest.py`, `live.py` (paper and live), `recorder.py`. |
101
+ | `src/backlight/db/` | SQLite schema (every run-scoped table cascades from `runs`, which carries a `mode` column), migrations, run repo with purge, batched record writer, report queries and metrics. |
102
+ | `src/backlight/hub/` | FastAPI routers (`api/`), background jobs, WebSocket event bus, hub config, secrets, pywebview shell. |
103
+ | `gui/` | Vue 3 + Vuetify + VueFlow UI: Dashboard, Pipelines workbench, Decision Trees, Backtest Lab, Runs, Live, Data, Settings (with the Connectors tab), and an in-app illustrated guide (`gui/public/guide.html`). |
104
+ | `charts/backlight/`, `Dockerfile` | Container image and Helm chart: one hub pod on a PVC, optional sign-in, optional cloudflared tunnel. |
105
+ | `docs/` | The proposal, architecture notes, connector setup, Kalshi plan. |
106
+
107
+ ## How a tick works
108
+
109
+ ```
110
+ trigger -> universe -> selector -> strategy (decision tree) -> refiners -> sizer -> execution -> final
111
+ ```
112
+
113
+ Every node has `PARAMETERS` rendered by the GUI, typed ports checked at compile
114
+ time, and tags. A strategy emits `Signal`s carrying the `DecisionPath` that
115
+ produced them; refiners veto or reshape and leave a note; the sizer turns the
116
+ signal into an `Intent`; execution builds `OrderRequest`s (with bracket
117
+ children); `output.final` applies buying power and per-underlying caps. Every
118
+ hop is recorded, so a fill walks back to the tree leaf and selector rule that
119
+ caused it, and reports attribute P&L per tag, per leaf, and per tree edge.
120
+
121
+ ## Modes
122
+
123
+ | Mode | Clock | Data | Execution |
124
+ | --- | --- | --- | --- |
125
+ | backtest | bar iterator | historical connector + Parquet cache | `sim` |
126
+ | paper | wall clock | market connector | `sim` or a venue's paper account |
127
+ | live | wall clock | market connector | venue (Schwab) |
128
+
129
+ Backtest runs can be purged from the Runs view or with `backlight purge`; paper
130
+ and live runs are kept unless purged by mode explicitly.
131
+
132
+ ## Writing a node
133
+
134
+ ```python
135
+ from backlight.pipeline.nodes.base import RefinerNode, register_node
136
+ from backlight.pipeline.params import Parameter
137
+
138
+ @register_node
139
+ class NoFridays(RefinerNode):
140
+ """Veto new entries on Fridays."""
141
+ kind = "refiner.no-fridays"
142
+ label = "No Fridays"
143
+ PARAMETERS = [Parameter("enabled_days", "number", "Days", default=4, min=0, max=6, step=1)]
144
+
145
+ def applies(self, signal, ctx):
146
+ return signal.action.value.startswith("open")
147
+
148
+ def apply(self, signal, ctx):
149
+ return self.veto("friday") if ctx.now.weekday() == 4 else signal
150
+ ```
151
+
152
+ The docstring is the node's documentation in the workbench: its first
153
+ paragraph is the palette description, the whole of it shows under **More**.
154
+ `PORT_DOCS = {"port": "..."}` says what a port means on this node. Text is
155
+ plain: blank lines split paragraphs, `- ` starts a list item, backticks mark
156
+ code.
157
+
158
+ Port types (`run`, `universe`, `signal`, `intent`, `order`) live in a registry
159
+ in `backlight.pipeline.ports`; clicking a port in the workbench opens its
160
+ documentation. A plugin registers its own from the same module:
161
+
162
+ ```python
163
+ from backlight.pipeline.ports import PortType, register_port_type
164
+
165
+ register_port_type(PortType(
166
+ "alert", "Alert", "A message for the operator.",
167
+ doc="Longer explanation for the port-type dialog.",
168
+ carries=Alert, # a dataclass: its fields are listed
169
+ field_docs={"text": "What to say."}, color="#e11d48"))
170
+ ```
171
+
172
+ The engine only moves values along the ports each node category handles, so
173
+ a plugin type is documented and wired in the editor, but nothing produces or
174
+ consumes it yet.
175
+
176
+ Drop the file in `~/.config/backlight/plugins/nodes/` or register it under the
177
+ `backlight.nodes` entry-point group. Connectors work the same way under
178
+ `plugins/connectors/` and the `backlight.connectors` group.
179
+
180
+ ## Pipeline agent
181
+
182
+ In the pipeline editor, **Agent** opens a chat that builds and edits the open
183
+ pipeline: it reads the node catalog and other pipelines, adds and wires nodes,
184
+ sets parameters and validates, and the canvas follows each edit (nothing is
185
+ saved until you press Save). Chats are per user and kept per pipeline.
186
+
187
+ When no node fits, the agent can write one. A written node loads only after:
188
+ a static check (allowed imports and calls, no private attributes or routes to
189
+ connectors, never an execution or output node), an import test in a separate
190
+ process with an empty environment, and approval by a reviewer agent. Approved
191
+ nodes live in `~/.config/backlight/plugins/agent-nodes/` and are listed (with
192
+ their code and review) under Admin → Settings → Agent, where an admin can
193
+ remove them.
194
+
195
+ Setup: `pip install -e ".[agent]"` (the Docker image includes it), then Admin →
196
+ Settings → Agent: provider (OpenAI or Anthropic), its API key, model, Enable.
197
+ OpenAI also covers any OpenAI-compatible endpoint through the base URL.
198
+ Anthropic is spoken natively (Claude's thinking is kept across turns, an
199
+ effort setting, prompt caching, and on Anthropic's own API a declined request
200
+ is retried on Anthropic's recommended fallback model). One hub-wide key per
201
+ provider; every signed-in user's chats use the active one.
202
+
203
+ The tools (`backlight.agent.tools`) are plain name + JSON schema + handler over a
204
+ server-side working pipeline, so another front end (an MCP server) can expose
205
+ the same set.
206
+
207
+ ## Paths
208
+
209
+ | What | Where | Override |
210
+ | --- | --- | --- |
211
+ | pipelines, trees, hub config, secrets, plugins | `~/.config/backlight/` | `BACKLIGHT_CONFIG_DIR` |
212
+ | SQLite database | `~/.backlight/backlight.db` | `BACKLIGHT_DATA_DIR`, `BACKLIGHT_DB` |
213
+ | Parquet bar cache, logs | `~/.cache/backlight/` | `BACKLIGHT_CACHE_DIR` |
214
+
215
+ ### Moving a cache to another instance
216
+
217
+ A provider's cached bars, option blocks and downloaded store (Massive flat
218
+ files, Databento's dataset) export to one tar and import anywhere, from the
219
+ Data tab (Export / import cache) or the CLI:
220
+
221
+ ```sh
222
+ backlight cache list # what each provider holds
223
+ backlight cache export --provider massive --provider massive_files -o massive.tar
224
+ backlight cache export --provider massive_files --timeframe 1d -o day.tar # only some timeframes
225
+ backlight cache import massive.tar # on the other instance
226
+ ssh a backlight cache export --provider massive -o - | backlight cache import - # or piped
227
+ ```
228
+
229
+ An import never deletes. Files already there are kept unless the archive's
230
+ copy is newer (`--mode newer`, the default; bar files merge instead), or
231
+ always replaced (`--mode replace`), or always kept (`--mode skip`). Store
232
+ files land in the importing instance's own `data_dir` for that connector.
233
+ The Data tab uploads in 32 MB pieces, so a proxy's body limit (Cloudflare:
234
+ 100 MB) doesn't get in the way.
235
+
236
+ ## Development
237
+
238
+ ```sh
239
+ make test # pytest
240
+ make smoke # every preset through a two-year synthetic backtest
241
+ make gui # rebuild the UI after editing gui/src
242
+ BACKLIGHT_HUB_DEBUG=1 backlight hub # WebKit inspector in the window
243
+ ```
@@ -0,0 +1,203 @@
1
+ # Backlight
2
+
3
+ Automated stock and options trading platform: pipelines of parameterized nodes
4
+ pick securities, decide how to trade them through tagged decision trees, refine
5
+ those decisions, and hand orders to a connector. One runner drives the same
6
+ pipeline in backtest, paper, or live mode. Built on the same stack as proxer:
7
+ pywebview + FastAPI + Vue 3 (Vuetify).
8
+
9
+ ## Quick start
10
+
11
+ ```sh
12
+ pip install py-backlight # or, from a checkout: make install (pip install -e .[dev] + build the UI)
13
+ backlight hub # opens the desktop window (or: backlight hub --headless)
14
+ ```
15
+
16
+ The PyPI distribution is `py-backlight` (`backlight` there is an old, empty
17
+ project); the package, the import and the command are all `backlight`. Extras:
18
+ `py-backlight[agent]` (the pipeline agent) and `py-backlight[databento]`.
19
+ `make check` builds the wheel with the UI bundled and runs `twine check`;
20
+ `make deploy` uploads it.
21
+
22
+ Upgrading from `trader`, the old name: the first run moves `~/.config/trader`,
23
+ `~/.trader` (and `trader.db` in it) and `~/.cache/trader` to their `backlight`
24
+ names, `TRADER_*` environment variables still work where no `BACKLIGHT_*`
25
+ one is set, and agent nodes are rewritten to import `backlight`.
26
+
27
+ Without the window, the API is at `http://127.0.0.1:8770/docs`.
28
+
29
+ Deployed on a cluster, headless, behind a Cloudflare tunnel and with sign-in
30
+ on: see [docs/deploy.md](docs/deploy.md) (Dockerfile, Helm chart in
31
+ `charts/backlight`). Sign-in is optional — off on the desktop unless you add a
32
+ user with `backlight auth add-user NAME`.
33
+
34
+ From the command line:
35
+
36
+ ```sh
37
+ backlight catalog # node types and connectors
38
+ backlight backtest preset-sma-cross --symbols SPY,QQQ --start 2022-01-01 --end 2023-12-31
39
+ backlight runs --mode backtest
40
+ backlight report <run_id> # markdown run report
41
+ backlight report <run_id_a> <run_id_b> # compare
42
+ backlight purge --older-than-days 30 --vacuum # delete old backtest runs
43
+ backlight fetch --provider massive --symbols SPY --start 2020-01-01 --end 2024-12-31
44
+ backlight flatfiles --start 2024-01-01 --end 2024-12-31 # Massive option minute files, once
45
+ backlight backtest preset-opt-0dte-condor --symbols SPY --provider alpaca --option-provider massive_files
46
+ ```
47
+
48
+ The first launch seeds fifteen preset pipelines (stock, options, and two branching
49
+ examples) and four decision trees into
50
+ `~/.config/backlight/`, and the `synthetic` data connector needs no network, so
51
+ the Backtest Lab works out of the box.
52
+
53
+ ## Layout
54
+
55
+ | Path | What |
56
+ | --- | --- |
57
+ | `src/backlight/types.py` | The objects that cross node ports: `SecurityId`, `Bar`, `Quote`, `OptionContract`, `Universe`, `Signal`, `Intent`, `OrderRequest`, `OrderEvent`, `Position`. |
58
+ | `src/backlight/pipeline/` | `params.py` (parameter schemas, from proxer), `nodes/` (trigger, universe, selector, strategy, refiner, sizer, execution, output, flow), `tree/` (decision tree model, safe expression language, action templates), `graph/` (pipeline docs, validation, compile, catalog, plugins, stores, presets), `engine.py` (the tick). |
59
+ | `src/backlight/connectors/` | `base.py` (the three interfaces), `sim` (fill model, brackets, option expiry), `synthetic` (offline random-walk bars, Black-Scholes chains, paper-mode market data), `csv`, `schwab`, `alpaca`, `massive`, `massive_files` (option flat files over S3), `kalshi` (plan only). |
60
+ | `src/backlight/runner/` | `portfolio.py` (ledger), `feed.py` (bar views for nodes and the sim), `backtest.py`, `live.py` (paper and live), `recorder.py`. |
61
+ | `src/backlight/db/` | SQLite schema (every run-scoped table cascades from `runs`, which carries a `mode` column), migrations, run repo with purge, batched record writer, report queries and metrics. |
62
+ | `src/backlight/hub/` | FastAPI routers (`api/`), background jobs, WebSocket event bus, hub config, secrets, pywebview shell. |
63
+ | `gui/` | Vue 3 + Vuetify + VueFlow UI: Dashboard, Pipelines workbench, Decision Trees, Backtest Lab, Runs, Live, Data, Settings (with the Connectors tab), and an in-app illustrated guide (`gui/public/guide.html`). |
64
+ | `charts/backlight/`, `Dockerfile` | Container image and Helm chart: one hub pod on a PVC, optional sign-in, optional cloudflared tunnel. |
65
+ | `docs/` | The proposal, architecture notes, connector setup, Kalshi plan. |
66
+
67
+ ## How a tick works
68
+
69
+ ```
70
+ trigger -> universe -> selector -> strategy (decision tree) -> refiners -> sizer -> execution -> final
71
+ ```
72
+
73
+ Every node has `PARAMETERS` rendered by the GUI, typed ports checked at compile
74
+ time, and tags. A strategy emits `Signal`s carrying the `DecisionPath` that
75
+ produced them; refiners veto or reshape and leave a note; the sizer turns the
76
+ signal into an `Intent`; execution builds `OrderRequest`s (with bracket
77
+ children); `output.final` applies buying power and per-underlying caps. Every
78
+ hop is recorded, so a fill walks back to the tree leaf and selector rule that
79
+ caused it, and reports attribute P&L per tag, per leaf, and per tree edge.
80
+
81
+ ## Modes
82
+
83
+ | Mode | Clock | Data | Execution |
84
+ | --- | --- | --- | --- |
85
+ | backtest | bar iterator | historical connector + Parquet cache | `sim` |
86
+ | paper | wall clock | market connector | `sim` or a venue's paper account |
87
+ | live | wall clock | market connector | venue (Schwab) |
88
+
89
+ Backtest runs can be purged from the Runs view or with `backlight purge`; paper
90
+ and live runs are kept unless purged by mode explicitly.
91
+
92
+ ## Writing a node
93
+
94
+ ```python
95
+ from backlight.pipeline.nodes.base import RefinerNode, register_node
96
+ from backlight.pipeline.params import Parameter
97
+
98
+ @register_node
99
+ class NoFridays(RefinerNode):
100
+ """Veto new entries on Fridays."""
101
+ kind = "refiner.no-fridays"
102
+ label = "No Fridays"
103
+ PARAMETERS = [Parameter("enabled_days", "number", "Days", default=4, min=0, max=6, step=1)]
104
+
105
+ def applies(self, signal, ctx):
106
+ return signal.action.value.startswith("open")
107
+
108
+ def apply(self, signal, ctx):
109
+ return self.veto("friday") if ctx.now.weekday() == 4 else signal
110
+ ```
111
+
112
+ The docstring is the node's documentation in the workbench: its first
113
+ paragraph is the palette description, the whole of it shows under **More**.
114
+ `PORT_DOCS = {"port": "..."}` says what a port means on this node. Text is
115
+ plain: blank lines split paragraphs, `- ` starts a list item, backticks mark
116
+ code.
117
+
118
+ Port types (`run`, `universe`, `signal`, `intent`, `order`) live in a registry
119
+ in `backlight.pipeline.ports`; clicking a port in the workbench opens its
120
+ documentation. A plugin registers its own from the same module:
121
+
122
+ ```python
123
+ from backlight.pipeline.ports import PortType, register_port_type
124
+
125
+ register_port_type(PortType(
126
+ "alert", "Alert", "A message for the operator.",
127
+ doc="Longer explanation for the port-type dialog.",
128
+ carries=Alert, # a dataclass: its fields are listed
129
+ field_docs={"text": "What to say."}, color="#e11d48"))
130
+ ```
131
+
132
+ The engine only moves values along the ports each node category handles, so
133
+ a plugin type is documented and wired in the editor, but nothing produces or
134
+ consumes it yet.
135
+
136
+ Drop the file in `~/.config/backlight/plugins/nodes/` or register it under the
137
+ `backlight.nodes` entry-point group. Connectors work the same way under
138
+ `plugins/connectors/` and the `backlight.connectors` group.
139
+
140
+ ## Pipeline agent
141
+
142
+ In the pipeline editor, **Agent** opens a chat that builds and edits the open
143
+ pipeline: it reads the node catalog and other pipelines, adds and wires nodes,
144
+ sets parameters and validates, and the canvas follows each edit (nothing is
145
+ saved until you press Save). Chats are per user and kept per pipeline.
146
+
147
+ When no node fits, the agent can write one. A written node loads only after:
148
+ a static check (allowed imports and calls, no private attributes or routes to
149
+ connectors, never an execution or output node), an import test in a separate
150
+ process with an empty environment, and approval by a reviewer agent. Approved
151
+ nodes live in `~/.config/backlight/plugins/agent-nodes/` and are listed (with
152
+ their code and review) under Admin → Settings → Agent, where an admin can
153
+ remove them.
154
+
155
+ Setup: `pip install -e ".[agent]"` (the Docker image includes it), then Admin →
156
+ Settings → Agent: provider (OpenAI or Anthropic), its API key, model, Enable.
157
+ OpenAI also covers any OpenAI-compatible endpoint through the base URL.
158
+ Anthropic is spoken natively (Claude's thinking is kept across turns, an
159
+ effort setting, prompt caching, and on Anthropic's own API a declined request
160
+ is retried on Anthropic's recommended fallback model). One hub-wide key per
161
+ provider; every signed-in user's chats use the active one.
162
+
163
+ The tools (`backlight.agent.tools`) are plain name + JSON schema + handler over a
164
+ server-side working pipeline, so another front end (an MCP server) can expose
165
+ the same set.
166
+
167
+ ## Paths
168
+
169
+ | What | Where | Override |
170
+ | --- | --- | --- |
171
+ | pipelines, trees, hub config, secrets, plugins | `~/.config/backlight/` | `BACKLIGHT_CONFIG_DIR` |
172
+ | SQLite database | `~/.backlight/backlight.db` | `BACKLIGHT_DATA_DIR`, `BACKLIGHT_DB` |
173
+ | Parquet bar cache, logs | `~/.cache/backlight/` | `BACKLIGHT_CACHE_DIR` |
174
+
175
+ ### Moving a cache to another instance
176
+
177
+ A provider's cached bars, option blocks and downloaded store (Massive flat
178
+ files, Databento's dataset) export to one tar and import anywhere, from the
179
+ Data tab (Export / import cache) or the CLI:
180
+
181
+ ```sh
182
+ backlight cache list # what each provider holds
183
+ backlight cache export --provider massive --provider massive_files -o massive.tar
184
+ backlight cache export --provider massive_files --timeframe 1d -o day.tar # only some timeframes
185
+ backlight cache import massive.tar # on the other instance
186
+ ssh a backlight cache export --provider massive -o - | backlight cache import - # or piped
187
+ ```
188
+
189
+ An import never deletes. Files already there are kept unless the archive's
190
+ copy is newer (`--mode newer`, the default; bar files merge instead), or
191
+ always replaced (`--mode replace`), or always kept (`--mode skip`). Store
192
+ files land in the importing instance's own `data_dir` for that connector.
193
+ The Data tab uploads in 32 MB pieces, so a proxy's body limit (Cloudflare:
194
+ 100 MB) doesn't get in the way.
195
+
196
+ ## Development
197
+
198
+ ```sh
199
+ make test # pytest
200
+ make smoke # every preset through a two-year synthetic backtest
201
+ make gui # rebuild the UI after editing gui/src
202
+ BACKLIGHT_HUB_DEBUG=1 backlight hub # WebKit inspector in the window
203
+ ```
@@ -0,0 +1,92 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ # `backlight` on PyPI is an empty 2012 project, so the distribution is
7
+ # py-backlight; the package, the CLI and the import are all `backlight`.
8
+ name = "py-backlight"
9
+ dynamic = ["version"]
10
+ description = "Backtest, paper trade and live trade stock and option strategies built from pipelines of nodes"
11
+ readme = "README.md"
12
+ requires-python = ">=3.11"
13
+ license = { text = "MIT" }
14
+ authors = [{ name = "Jason Berger", email = "berge472@gmail.com" }]
15
+ keywords = ["trading", "backtesting", "options", "paper-trading", "algorithmic-trading"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Operating System :: MacOS",
23
+ "Topic :: Office/Business :: Financial :: Investment",
24
+ ]
25
+ dependencies = [
26
+ "fastapi>=0.110",
27
+ "uvicorn>=0.27",
28
+ "pydantic>=2.5",
29
+ "pywebview>=5.0",
30
+ "pyyaml>=6.0",
31
+ "httpx>=0.27",
32
+ "websockets>=12",
33
+ "numpy>=1.26",
34
+ "pyarrow>=15",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ # `backlight databento-import`: reads Databento batch downloads (DBN metadata)
39
+ databento = [
40
+ "databento-dbn>=0.20",
41
+ ]
42
+ # the pipeline agent (Pipelines → chat): OpenAI (or an OpenAI-compatible API) or Anthropic
43
+ agent = [
44
+ "openai>=1.40",
45
+ "anthropic>=1.0",
46
+ ]
47
+ dev = [
48
+ "pytest>=7",
49
+ "pytest-timeout>=2",
50
+ "ruff>=0.4",
51
+ ]
52
+
53
+ [project.scripts]
54
+ backlight = "backlight.cli:main"
55
+
56
+ [project.urls]
57
+ Homepage = "https://gitlab.com/JasonBerger/backlight"
58
+ Documentation = "https://gitlab.com/JasonBerger/backlight/-/blob/main/README.md"
59
+ Repository = "https://gitlab.com/JasonBerger/backlight"
60
+ Issues = "https://gitlab.com/JasonBerger/backlight/-/issues"
61
+ Source = "https://gitlab.com/JasonBerger/backlight"
62
+
63
+ [project.entry-points."backlight.nodes"]
64
+ # Third-party node plugins register here: name = "package.module".
65
+
66
+ [project.entry-points."backlight.connectors"]
67
+ # Third-party connector plugins register here: name = "package.module".
68
+
69
+ [tool.hatch.version]
70
+ path = "src/backlight/version.py"
71
+
72
+ [tool.hatch.build]
73
+ # The built UI, copied in by `make dist` (git ignores it, hence an artifact).
74
+ # Absent, the wheel still builds and the hub serves gui/dist from a checkout.
75
+ artifacts = ["src/backlight/_gui/**"]
76
+
77
+ [tool.hatch.build.targets.wheel]
78
+ packages = ["src/backlight"]
79
+
80
+ [tool.hatch.build.targets.sdist]
81
+ include = ["src/backlight", "README.md", "pyproject.toml"]
82
+
83
+ [tool.ruff]
84
+ line-length = 120
85
+ target-version = "py311"
86
+
87
+ [tool.ruff.lint]
88
+ select = ["E", "F", "W"]
89
+ per-file-ignores = { "tests/*" = ["E501"], "src/backlight/db/records.py" = ["E501"] }
90
+
91
+ [tool.pytest.ini_options]
92
+ testpaths = ["tests"]
@@ -0,0 +1,14 @@
1
+ """Backlight: automated stock and options trading platform.
2
+
3
+ Pipelines of parameterized nodes pick securities, decide how to trade them
4
+ through tagged decision trees, refine those decisions, and hand orders to a
5
+ connector. One runner drives the same pipeline in backtest, paper, or live
6
+ mode. See ``docs/`` for the proposal this implements.
7
+ """
8
+
9
+ from backlight.paths import adopt_legacy_env
10
+ from backlight.version import __version__
11
+
12
+ adopt_legacy_env()
13
+
14
+ __all__ = ["__version__"]
@@ -0,0 +1 @@
1
+ import{_ as N,aR as T,c as m,d as s,s as k,u as v,F as U,e as t,w as o,h as x,f as n,l as V,k as u,j as q,a3 as L,m as R,n as r,a4 as g,t as h,i as C,H as W}from"./index-BfAt6891.js";import{C as D,S as F,W as j}from"./WebKeysPanel-C63Vkpqw.js";import"./SymbolsInput-BZQQUVay.js";const E={class:"page"},H={class:"page-head"},I={class:"muted"},K={key:0,class:"card muted"},M={key:0,class:"apanel"},Y={key:3,class:"agrid"},G={class:"card"},J={class:"card-title"},O={class:"card"},Q={class:"card-title"},X={__name:"AccountView",setup(Z){const w=T(),c=L(),P=R(),d=V({get:()=>["brokers","symbols","webkeys"].includes(c.query.tab)?c.query.tab:"profile",set:l=>P.replace({query:{...c.query,tab:l==="profile"?void 0:l}})});async function S(l){w.global.name.value=l,await h(()=>C.putSettings({ui:{theme:l}}))}const a=q({current:"",next:"",again:"",busy:!1}),b=V(()=>a.value.next?a.value.next.length<8?"at least 8 characters":a.value.again&&a.value.again!==a.value.next?"the two new passwords differ":"":"");async function z(){a.value.busy=!0;const l=await h(()=>C.changePassword(a.value.current,a.value.next),null);a.value.busy=!1,l&&(W("password changed: sign in again with the new one","success"),setTimeout(()=>location.assign("/login"),1200))}return(l,e)=>{const p=u("v-tab"),A=u("v-tabs"),_=u("v-icon"),y=u("v-btn"),B=u("v-btn-toggle"),f=u("v-text-field");return r(),m("div",E,[s("div",H,[e[4]||(e[4]=s("h1",null,"Account",-1)),s("span",I,k(v(g).name)+" · "+k(v(g).role),1)]),v(g).auth?(r(),m(U,{key:1},[t(A,{modelValue:d.value,"onUpdate:modelValue":e[0]||(e[0]=i=>d.value=i),density:"compact",color:"primary",class:"mb-4 atabs"},{default:o(()=>[t(p,{value:"profile","prepend-icon":"mdi-account-outline"},{default:o(()=>[...e[5]||(e[5]=[n("Profile",-1)])]),_:1}),t(p,{value:"brokers","prepend-icon":"mdi-bank-outline"},{default:o(()=>[...e[6]||(e[6]=[n("My brokers",-1)])]),_:1}),t(p,{value:"symbols","prepend-icon":"mdi-format-list-group"},{default:o(()=>[...e[7]||(e[7]=[n("Symbol sets",-1)])]),_:1}),t(p,{value:"webkeys","prepend-icon":"mdi-key-chain-variant"},{default:o(()=>[...e[8]||(e[8]=[n("Web keys",-1)])]),_:1})]),_:1},8,["modelValue"]),d.value==="brokers"?(r(),m("div",M,[t(D,{mine:""})])):d.value==="symbols"?(r(),x(F,{key:1})):d.value==="webkeys"?(r(),x(j,{key:2})):(r(),m("div",Y,[s("div",G,[s("div",J,[t(_,{icon:"mdi-theme-light-dark",size:"14"}),e[9]||(e[9]=n("Theme",-1))]),t(B,{"model-value":v(w).global.name.value,mandatory:"",density:"compact",variant:"outlined",divided:"","onUpdate:modelValue":S},{default:o(()=>[t(y,{value:"dark",size:"small","prepend-icon":"mdi-weather-night"},{default:o(()=>[...e[10]||(e[10]=[n("Dark",-1)])]),_:1}),t(y,{value:"light",size:"small","prepend-icon":"mdi-white-balance-sunny"},{default:o(()=>[...e[11]||(e[11]=[n("Light",-1)])]),_:1})]),_:1},8,["model-value"]),e[12]||(e[12]=s("div",{class:"muted mt-3",style:{"font-size":"11.5px"}}," Your own: the theme, Backtest Lab and Live form values, and campaign defaults are kept for you alone. ",-1))]),s("div",O,[s("div",Q,[t(_,{icon:"mdi-form-textbox-password",size:"14"}),e[13]||(e[13]=n("Password",-1))]),t(f,{modelValue:a.value.current,"onUpdate:modelValue":e[1]||(e[1]=i=>a.value.current=i),type:"password",label:"Current password",autocomplete:"current-password",class:"mb-2"},null,8,["modelValue"]),t(f,{modelValue:a.value.next,"onUpdate:modelValue":e[2]||(e[2]=i=>a.value.next=i),type:"password",label:"New password",autocomplete:"new-password",class:"mb-2"},null,8,["modelValue"]),t(f,{modelValue:a.value.again,"onUpdate:modelValue":e[3]||(e[3]=i=>a.value.again=i),type:"password",label:"New password again",autocomplete:"new-password","error-messages":b.value?[b.value]:[]},null,8,["modelValue","error-messages"]),t(y,{color:"primary",variant:"flat",class:"mt-2",loading:a.value.busy,disabled:!a.value.current||!a.value.next||a.value.next!==a.value.again||!!b.value,onClick:z},{default:o(()=>[...e[14]||(e[14]=[n("Change password",-1)])]),_:1},8,["loading","disabled"]),e[15]||(e[15]=s("div",{class:"muted mt-2",style:{"font-size":"11.5px"}},"Changing it signs you out everywhere.",-1))])]))],64)):(r(),m("div",K,"Sign-in is off on this hub: there is one local admin, and everything is in Admin."))])}}},te=N(X,[["__scopeId","data-v-e180d83b"]]);export{te as default};
@@ -0,0 +1 @@
1
+ .agrid[data-v-e180d83b]{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:start;max-width:900px}.apanel[data-v-e180d83b]{max-width:1100px}.atabs[data-v-e180d83b]{border-bottom:1px solid var(--line)}@media(max-width:800px){.agrid[data-v-e180d83b]{grid-template-columns:1fr}}
@@ -0,0 +1 @@
1
+ .g2[data-v-de158446]{display:grid;grid-template-columns:1fr 1fr;gap:16px 12px;padding-top:6px}.g4[data-v-de158446]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px 10px;padding-top:6px}.g6[data-v-de158446]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px 10px;padding-top:6px;margin-bottom:12px}.g4+.muted[data-v-de158446]{margin-top:16px}.sets[data-v-de158446]{display:flex;flex-direction:column;gap:6px}.set[data-v-de158446]{border:1px solid var(--line);border-radius:8px;padding:8px 10px;cursor:pointer;font-size:12.5px}.set[data-v-de158446]:hover{background:var(--panel-2)}.set.on[data-v-de158446]{border-color:var(--accent);background:#4f8cff14}.chips[data-v-de158446]{display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin-top:4px}.small[data-v-de158446]{font-size:11.5px}.newset[data-v-de158446]{border:1px dashed var(--line);border-radius:8px;padding:10px}.small[data-v-f109decf]{font-size:11.5px}.plist[data-v-f109decf]{display:flex;flex-direction:column;gap:3px}.pitem[data-v-f109decf]{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;border:1px solid transparent;font-size:12.5px}.pitem[data-v-f109decf]:hover{background:var(--panel-2)}.pitem.on[data-v-f109decf]{border-color:#4f8cff80;background:#4f8cff14}.bandchart[data-v-ccd75fcf]{position:relative}svg[data-v-ccd75fcf]{width:100%;display:block}.grid line[data-v-ccd75fcf]{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke}.one[data-v-ccd75fcf]{stroke:var(--muted);stroke-dasharray:3 3;vector-effect:non-scaling-stroke;opacity:.6}.cross[data-v-ccd75fcf]{stroke:var(--muted);vector-effect:non-scaling-stroke}.axis text[data-v-ccd75fcf]{fill:var(--muted);font-size:11px}.readout[data-v-ccd75fcf]{position:absolute;top:8px;right:24px;background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:6px 8px;font-size:11.5px;pointer-events:none;width:430px;opacity:.96}.readout[data-v-ccd75fcf]:not(.right){right:auto;left:64px}.rr[data-v-ccd75fcf]{display:flex;gap:6px;align-items:center}.rr .nm[data-v-ccd75fcf]{flex:1}.sw[data-v-ccd75fcf]{width:10px;height:10px;border-radius:2px;display:inline-block;flex:0 0 auto}.legend[data-v-ccd75fcf]{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:11.5px;margin-top:6px}.lg[data-v-ccd75fcf]{display:inline-flex;align-items:center;gap:6px;cursor:default;padding:1px 4px;border-radius:4px}.lg.on[data-v-ccd75fcf]{background:var(--panel-2)}.lg.dim[data-v-ccd75fcf]{opacity:.45}.hm-tools[data-v-5cc040ef]{display:flex;align-items:center;flex-wrap:wrap;gap:4px;margin-bottom:8px;font-size:12px}.hm-wrap[data-v-5cc040ef]{overflow-x:auto}.hm[data-v-5cc040ef]{border-collapse:separate;border-spacing:2px;font-size:11.5px}.hm th.fam[data-v-5cc040ef]{font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:500;border-bottom:1px solid var(--line);padding:2px 4px}.hm th.colh[data-v-5cc040ef]{font-weight:500;color:var(--muted);padding:2px 4px;max-width:90px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.hm th.rowh[data-v-5cc040ef]{text-align:left;font-weight:400;min-width:180px;max-width:220px;padding-right:8px}.hm td[data-v-5cc040ef]{min-width:64px;height:34px;text-align:center;border-radius:4px;cursor:pointer;font-variant-numeric:tabular-nums}.hm td.empty[data-v-5cc040ef]{cursor:default;background:var(--panel-2)!important}.hm td.sel[data-v-5cc040ef]{outline:2px solid var(--accent)}.hm-note[data-v-5cc040ef]{font-size:11px;margin-top:6px}.ev-grid[data-v-c525c5bd]{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px}.ev[data-v-c525c5bd]{border:1px solid var(--line);border-radius:8px;padding:8px}.ev-h[data-v-c525c5bd]{font-size:12px;margin-bottom:4px}svg[data-v-c525c5bd]{width:100%;display:block}.zero[data-v-c525c5bd]{stroke:var(--line)}.day0[data-v-c525c5bd]{stroke:var(--muted);stroke-dasharray:2 3}.bench[data-v-c525c5bd]{stroke:var(--muted);stroke-dasharray:5 4;fill:none;stroke-width:1.2}.ax[data-v-c525c5bd]{fill:var(--muted);font-size:10px}.ev-row[data-v-c525c5bd]{display:flex;gap:6px;align-items:center;font-size:11.5px}.ev-row .clip[data-v-c525c5bd]{flex:1}.sw[data-v-c525c5bd]{width:10px;height:10px;border-radius:2px;flex:0 0 auto}.ev-note[data-v-c525c5bd]{font-size:11px;margin-top:6px}.br[data-v-861b6cb9]{fill:#f59e0b2e}.one[data-v-861b6cb9]{stroke:var(--muted);stroke-dasharray:3 3;vector-effect:non-scaling-stroke;opacity:.6}.ax[data-v-861b6cb9]{fill:var(--muted);font-size:11px}.runs-n[data-v-e1cd547d]{font-size:11px;opacity:.8}.vis-pick[data-v-e1cd547d]{flex:0 0 160px}.lab-grid[data-v-e1cd547d]{display:grid;grid-template-columns:300px minmax(0,1fr);gap:12px;align-items:start}.rail[data-v-e1cd547d]{display:flex;flex-direction:column;gap:10px}.rail .card[data-v-e1cd547d]{margin:0}.results[data-v-e1cd547d]{min-height:520px}.rhead[data-v-e1cd547d]{display:flex;align-items:center;border-bottom:1px solid var(--line);margin:-4px 0 8px}.tabbody[data-v-e1cd547d]{padding:4px 2px}.manage[data-v-e1cd547d]{font-size:11px;color:var(--accent);text-decoration:none;cursor:pointer}.manage[data-v-e1cd547d]:hover{text-decoration:underline}.src[data-v-e1cd547d]{display:flex;align-items:center;gap:6px;font-size:12.5px;flex-wrap:wrap}.dot[data-v-e1cd547d]{display:inline-block;width:8px;height:8px;border-radius:50%;background:#ef4444}.dot.ok[data-v-e1cd547d]{background:#22c55e}.g2[data-v-e1cd547d]{display:grid;grid-template-columns:1fr 1fr;gap:8px}.small[data-v-e1cd547d]{font-size:11.5px}.note[data-v-e1cd547d]{font-size:11.5px;color:var(--muted);display:flex;gap:4px;align-items:flex-start;flex-wrap:wrap}.chips[data-v-e1cd547d]{display:flex;flex-wrap:wrap;gap:4px;align-items:center}.tray[data-v-e1cd547d]{display:flex;flex-direction:column;gap:4px}.tchip[data-v-e1cd547d]{display:flex;align-items:center;gap:6px;padding:4px 8px;border:1px solid var(--line);border-radius:999px;font-size:12.5px}.tchip .clip[data-v-e1cd547d]{flex:1}.tchip.missing[data-v-e1cd547d]{border-style:dashed;opacity:.7}.tchip .mx[data-v-e1cd547d]{cursor:pointer;opacity:.5}.tchip .mx[data-v-e1cd547d]:hover{opacity:1}.sympv[data-v-e1cd547d]{font-size:12px;display:flex;flex-direction:column;gap:3px}.symchips[data-v-e1cd547d]{display:flex;flex-wrap:wrap;gap:3px}.symchip[data-v-e1cd547d]{font-size:11px;padding:1px 6px;border-radius:4px;background:var(--panel-2);border:1px solid var(--line)}.prog[data-v-e1cd547d]{padding:6px 2px 10px;border-bottom:1px solid var(--line);margin-bottom:10px}.prow[data-v-e1cd547d]{display:grid;grid-template-columns:minmax(0,1.6fr) 74px minmax(0,1fr) 100px 100px;gap:10px;align-items:center;font-size:12px;padding:3px 0}.prow-data[data-v-e1cd547d]{grid-column:1 / -1;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--muted)}.empty[data-v-e1cd547d]{text-align:center;padding:70px 40px;display:flex;flex-direction:column;gap:8px;align-items:center;max-width:620px;margin:0 auto}.sum[data-v-e1cd547d]{width:100%;border-collapse:collapse;font-size:12.5px}.sum th[data-v-e1cd547d]{text-align:left;font-weight:500;color:var(--muted);font-size:11.5px;padding:6px 8px;border-bottom:1px solid var(--line)}.sum td[data-v-e1cd547d]{padding:6px 8px;border-bottom:1px solid var(--line);vertical-align:top}.sum .r[data-v-e1cd547d]{text-align:right;font-variant-numeric:tabular-nums}.sum tr.clickable[data-v-e1cd547d]{cursor:pointer}.sum tr.clickable:hover td[data-v-e1cd547d]{background:var(--panel-2)}.range[data-v-e1cd547d]{font-size:10.5px}.live i[data-v-e1cd547d]{font-size:10.5px;opacity:.7}.sw[data-v-e1cd547d]{width:10px;height:10px;border-radius:2px;display:inline-block;margin-right:6px}.cell-detail[data-v-e1cd547d]{border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:12.5px}.sec-h[data-v-e1cd547d]{font-size:11px;text-transform:uppercase;letter-spacing:.04em;margin:12px 0 6px}.fgrid[data-v-e1cd547d]{display:grid;gap:16px 12px}.fgrid+.fgrid[data-v-e1cd547d]{margin-top:16px}.adv-dlg .sec-h[data-v-e1cd547d]{margin:20px 0 10px}.adv-dlg .sec-h[data-v-e1cd547d]:first-child{margin-top:6px}.adv-dlg .checks[data-v-e1cd547d]{margin-top:8px}.adv-dlg[data-v-e1cd547d] .v-card-text{padding-top:8px!important}.adv-row[data-v-e1cd547d]{display:flex;align-items:center;gap:8px}.adv-row .clip[data-v-e1cd547d]{flex:1;min-width:0}.fgrid.g3[data-v-e1cd547d]{grid-template-columns:repeat(3,minmax(0,1fr))}.fgrid.g4[data-v-e1cd547d]{grid-template-columns:repeat(4,minmax(0,1fr))}.checks[data-v-e1cd547d]{display:flex;flex-wrap:wrap;gap:0 14px}.rcol[data-v-e1cd547d]{min-width:0}.setworlds[data-v-e1cd547d]{display:flex;flex-wrap:wrap;gap:2px 8px;font-size:11.5px}.setworlds .ctl[data-v-e1cd547d]{color:var(--muted)}.batches td.pl[data-v-e1cd547d]{max-width:420px}.batches tr.active td[data-v-e1cd547d]{background:#4f8cff1a}.batches .del[data-v-e1cd547d]{opacity:.45}.batches tr:hover .del[data-v-e1cd547d]{opacity:1}@media(max-width:1100px){.lab-grid[data-v-e1cd547d]{grid-template-columns:1fr}}