opencode-pyneruntime 6.6.4__py3-none-any.whl

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 (261) hide show
  1. opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
  2. opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
  3. opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
  4. opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
  5. opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
  6. opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
  7. opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
  8. pynecore/__init__.py +6 -0
  9. pynecore/cli/__init__.py +2 -0
  10. pynecore/cli/app.py +238 -0
  11. pynecore/cli/commands/__init__.py +343 -0
  12. pynecore/cli/commands/benchmark.py +186 -0
  13. pynecore/cli/commands/compile.py +198 -0
  14. pynecore/cli/commands/data.py +857 -0
  15. pynecore/cli/commands/debug.py +63 -0
  16. pynecore/cli/commands/optimize.py +956 -0
  17. pynecore/cli/commands/plugin.py +242 -0
  18. pynecore/cli/commands/run.py +2006 -0
  19. pynecore/cli/pluggable.py +132 -0
  20. pynecore/cli/utils/__init__.py +0 -0
  21. pynecore/cli/utils/api_error_handler.py +168 -0
  22. pynecore/cli/utils/broker_picker.py +330 -0
  23. pynecore/cli/utils/error_hook.py +28 -0
  24. pynecore/cli/utils/keyreader.py +178 -0
  25. pynecore/cli/utils/provider_picker.py +19 -0
  26. pynecore/cli/utils/symbol_browser.py +1149 -0
  27. pynecore/core/__init__.py +0 -0
  28. pynecore/core/aggregator.py +257 -0
  29. pynecore/core/bar_magnifier.py +168 -0
  30. pynecore/core/broker/__init__.py +64 -0
  31. pynecore/core/broker/defaults.py +113 -0
  32. pynecore/core/broker/disappearance.py +927 -0
  33. pynecore/core/broker/emulator.py +345 -0
  34. pynecore/core/broker/exceptions.py +346 -0
  35. pynecore/core/broker/idempotency.py +401 -0
  36. pynecore/core/broker/intent_builder.py +334 -0
  37. pynecore/core/broker/journal.py +1785 -0
  38. pynecore/core/broker/models.py +1600 -0
  39. pynecore/core/broker/native_failsafe_manager.py +1436 -0
  40. pynecore/core/broker/one_way_emulator.py +1128 -0
  41. pynecore/core/broker/position.py +787 -0
  42. pynecore/core/broker/run_identity.py +126 -0
  43. pynecore/core/broker/software_entry_stop_engine.py +351 -0
  44. pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
  45. pynecore/core/broker/spot_inventory.py +1327 -0
  46. pynecore/core/broker/storage.py +2655 -0
  47. pynecore/core/broker/store_helpers.py +2161 -0
  48. pynecore/core/broker/sync_engine.py +16070 -0
  49. pynecore/core/broker/validation.py +382 -0
  50. pynecore/core/class_property.py +7 -0
  51. pynecore/core/config.py +392 -0
  52. pynecore/core/csv_file.py +547 -0
  53. pynecore/core/currency.py +262 -0
  54. pynecore/core/data_converter.py +1002 -0
  55. pynecore/core/datetime.py +296 -0
  56. pynecore/core/download_info.py +71 -0
  57. pynecore/core/download_runner.py +274 -0
  58. pynecore/core/htf_aggregator.py +181 -0
  59. pynecore/core/import_hook.py +358 -0
  60. pynecore/core/instance_state.py +494 -0
  61. pynecore/core/live_ltf_collector.py +442 -0
  62. pynecore/core/live_ltf_window.py +189 -0
  63. pynecore/core/live_runner.py +1347 -0
  64. pynecore/core/module_property.py +26 -0
  65. pynecore/core/ohlcv_file.py +1888 -0
  66. pynecore/core/overload.py +371 -0
  67. pynecore/core/pine_cast.py +113 -0
  68. pynecore/core/pine_export.py +95 -0
  69. pynecore/core/pine_method.py +244 -0
  70. pynecore/core/pine_range.py +86 -0
  71. pynecore/core/pine_udt.py +69 -0
  72. pynecore/core/plugin/__init__.py +394 -0
  73. pynecore/core/plugin/broker.py +781 -0
  74. pynecore/core/plugin/cli.py +96 -0
  75. pynecore/core/plugin/live_provider.py +208 -0
  76. pynecore/core/plugin/provider.py +331 -0
  77. pynecore/core/provider_string.py +148 -0
  78. pynecore/core/random.py +40 -0
  79. pynecore/core/resampler.py +686 -0
  80. pynecore/core/safe_convert.py +64 -0
  81. pynecore/core/script.py +1011 -0
  82. pynecore/core/script_runner.py +3202 -0
  83. pynecore/core/security.py +1749 -0
  84. pynecore/core/security_process.py +1253 -0
  85. pynecore/core/security_shm.py +456 -0
  86. pynecore/core/series.py +417 -0
  87. pynecore/core/strategy_stats.py +669 -0
  88. pynecore/core/symbol_map.py +134 -0
  89. pynecore/core/syminfo.py +505 -0
  90. pynecore/core/viz.py +591 -0
  91. pynecore/lib/__init__.py +1771 -0
  92. pynecore/lib/_fixnan.py +32 -0
  93. pynecore/lib/_math_stateful.py +202 -0
  94. pynecore/lib/_timeframe_change.py +101 -0
  95. pynecore/lib/adjustment.py +6 -0
  96. pynecore/lib/alert.py +39 -0
  97. pynecore/lib/alert.pyi +14 -0
  98. pynecore/lib/array.py +1051 -0
  99. pynecore/lib/barmerge.py +60 -0
  100. pynecore/lib/barstate.py +30 -0
  101. pynecore/lib/box.py +415 -0
  102. pynecore/lib/chart.py +128 -0
  103. pynecore/lib/color.py +152 -0
  104. pynecore/lib/color.pyi +50 -0
  105. pynecore/lib/currency.py +62 -0
  106. pynecore/lib/dayofweek.py +36 -0
  107. pynecore/lib/dayofweek.pyi +18 -0
  108. pynecore/lib/display.py +8 -0
  109. pynecore/lib/dividends.py +9 -0
  110. pynecore/lib/earnings.py +11 -0
  111. pynecore/lib/extend.py +6 -0
  112. pynecore/lib/font.py +5 -0
  113. pynecore/lib/footprint.py +79 -0
  114. pynecore/lib/format.py +11 -0
  115. pynecore/lib/hline.py +67 -0
  116. pynecore/lib/hline.pyi +24 -0
  117. pynecore/lib/label.py +409 -0
  118. pynecore/lib/line.py +433 -0
  119. pynecore/lib/linefill.py +93 -0
  120. pynecore/lib/location.py +11 -0
  121. pynecore/lib/log.py +362 -0
  122. pynecore/lib/map.py +150 -0
  123. pynecore/lib/math.py +385 -0
  124. pynecore/lib/matrix.py +708 -0
  125. pynecore/lib/order.py +8 -0
  126. pynecore/lib/pivotpointtype.py +8 -0
  127. pynecore/lib/plot.py +95 -0
  128. pynecore/lib/plot.pyi +33 -0
  129. pynecore/lib/polyline.py +91 -0
  130. pynecore/lib/position.py +15 -0
  131. pynecore/lib/request.py +281 -0
  132. pynecore/lib/runtime.py +5 -0
  133. pynecore/lib/scale.py +9 -0
  134. pynecore/lib/session.py +267 -0
  135. pynecore/lib/session.pyi +12 -0
  136. pynecore/lib/shape.py +18 -0
  137. pynecore/lib/size.py +12 -0
  138. pynecore/lib/splits.py +4 -0
  139. pynecore/lib/strategy/__init__.py +4778 -0
  140. pynecore/lib/strategy/closedtrades.py +347 -0
  141. pynecore/lib/strategy/closedtrades.pyi +53 -0
  142. pynecore/lib/strategy/commission.py +9 -0
  143. pynecore/lib/strategy/direction.py +9 -0
  144. pynecore/lib/strategy/oca.py +13 -0
  145. pynecore/lib/strategy/opentrades.py +281 -0
  146. pynecore/lib/strategy/opentrades.pyi +49 -0
  147. pynecore/lib/strategy/risk.py +109 -0
  148. pynecore/lib/string.py +649 -0
  149. pynecore/lib/syminfo.py +84 -0
  150. pynecore/lib/ta.py +2230 -0
  151. pynecore/lib/table.py +290 -0
  152. pynecore/lib/text.py +17 -0
  153. pynecore/lib/ticker.py +207 -0
  154. pynecore/lib/timeframe.py +293 -0
  155. pynecore/lib/volume_row.py +67 -0
  156. pynecore/lib/xloc.py +4 -0
  157. pynecore/lib/yloc.py +5 -0
  158. pynecore/providers/__init__.py +0 -0
  159. pynecore/providers/ccxt.py +664 -0
  160. pynecore/providers/replay.py +187 -0
  161. pynecore/pynesys/__init__.py +0 -0
  162. pynecore/pynesys/api.py +498 -0
  163. pynecore/pynesys/compiler.py +112 -0
  164. pynecore/standalone.py +99 -0
  165. pynecore/testing/__init__.py +1 -0
  166. pynecore/testing/broker_lab/__init__.py +41 -0
  167. pynecore/testing/broker_lab/__main__.py +5 -0
  168. pynecore/testing/broker_lab/cli.py +87 -0
  169. pynecore/testing/broker_lab/generate.py +47 -0
  170. pynecore/testing/broker_lab/model.py +84 -0
  171. pynecore/testing/broker_lab/reference.py +645 -0
  172. pynecore/testing/broker_lab/runner.py +372 -0
  173. pynecore/testing/broker_lab/scheduler.py +50 -0
  174. pynecore/testing/broker_lab/subprocess.py +73 -0
  175. pynecore/transformers/__init__.py +0 -0
  176. pynecore/transformers/builtin_shadow.py +136 -0
  177. pynecore/transformers/closure_arguments_transformer.py +428 -0
  178. pynecore/transformers/display_rewrite.py +140 -0
  179. pynecore/transformers/dynamic_default.py +147 -0
  180. pynecore/transformers/function_isolation.py +757 -0
  181. pynecore/transformers/import_lifter.py +61 -0
  182. pynecore/transformers/import_normalizer.py +328 -0
  183. pynecore/transformers/inline_series_hoist.py +178 -0
  184. pynecore/transformers/input_transformer.py +175 -0
  185. pynecore/transformers/lib_series.py +201 -0
  186. pynecore/transformers/locations.py +70 -0
  187. pynecore/transformers/module_properties.json +3387 -0
  188. pynecore/transformers/module_property.py +221 -0
  189. pynecore/transformers/ne_guard.py +70 -0
  190. pynecore/transformers/persistent.py +320 -0
  191. pynecore/transformers/persistent_series.py +76 -0
  192. pynecore/transformers/safe_convert_transformer.py +97 -0
  193. pynecore/transformers/safe_division_transformer.py +95 -0
  194. pynecore/transformers/script_requirements.py +308 -0
  195. pynecore/transformers/security.py +752 -0
  196. pynecore/transformers/security_instantiation.py +274 -0
  197. pynecore/transformers/series.py +275 -0
  198. pynecore/transformers/slot_layout.py +381 -0
  199. pynecore/transformers/type_checking_stripper.py +25 -0
  200. pynecore/transformers/unused_series_detector.py +267 -0
  201. pynecore/types/__init__.py +21 -0
  202. pynecore/types/alert.py +5 -0
  203. pynecore/types/barmerge.py +5 -0
  204. pynecore/types/base.py +39 -0
  205. pynecore/types/box.py +37 -0
  206. pynecore/types/chart.py +17 -0
  207. pynecore/types/color.py +107 -0
  208. pynecore/types/currency.py +5 -0
  209. pynecore/types/datetime.py +6 -0
  210. pynecore/types/display.py +5 -0
  211. pynecore/types/dividends.py +5 -0
  212. pynecore/types/earnings.py +5 -0
  213. pynecore/types/extend.py +5 -0
  214. pynecore/types/font.py +5 -0
  215. pynecore/types/footprint.py +41 -0
  216. pynecore/types/format.py +5 -0
  217. pynecore/types/hline.py +24 -0
  218. pynecore/types/ib_persistent.py +8 -0
  219. pynecore/types/ib_persistent.pyi +10 -0
  220. pynecore/types/label.py +35 -0
  221. pynecore/types/line.py +32 -0
  222. pynecore/types/linefill.py +13 -0
  223. pynecore/types/location.py +5 -0
  224. pynecore/types/matrix.py +999 -0
  225. pynecore/types/na.py +237 -0
  226. pynecore/types/na.pyi +83 -0
  227. pynecore/types/ohlcv.py +12 -0
  228. pynecore/types/order.py +5 -0
  229. pynecore/types/persistent.py +8 -0
  230. pynecore/types/persistent.pyi +13 -0
  231. pynecore/types/pine_types.py +11 -0
  232. pynecore/types/pine_types.pyi +15 -0
  233. pynecore/types/pivotpointtype.py +5 -0
  234. pynecore/types/plot.py +12 -0
  235. pynecore/types/plot_meta.py +60 -0
  236. pynecore/types/polyline.py +40 -0
  237. pynecore/types/position.py +5 -0
  238. pynecore/types/scale.py +5 -0
  239. pynecore/types/script_type.py +15 -0
  240. pynecore/types/series.py +23 -0
  241. pynecore/types/series.pyi +19 -0
  242. pynecore/types/session.py +35 -0
  243. pynecore/types/shape.py +5 -0
  244. pynecore/types/size.py +5 -0
  245. pynecore/types/source.py +33 -0
  246. pynecore/types/splits.py +5 -0
  247. pynecore/types/strategy.py +45 -0
  248. pynecore/types/table.py +87 -0
  249. pynecore/types/text.py +13 -0
  250. pynecore/types/type_checker.py +7 -0
  251. pynecore/types/type_checker.pyi +48 -0
  252. pynecore/types/volume_row.py +36 -0
  253. pynecore/types/weekdays.py +11 -0
  254. pynecore/types/xloc.py +5 -0
  255. pynecore/types/yloc.py +5 -0
  256. pynecore/utils/__init__.py +0 -0
  257. pynecore/utils/file_utils.py +50 -0
  258. pynecore/utils/rich/__init__.py +0 -0
  259. pynecore/utils/rich/date_column.py +25 -0
  260. pynecore/utils/sequence_view.py +92 -0
  261. pynecore/utils/stdlib_checker.py +17 -0
pynecore/lib/log.py ADDED
@@ -0,0 +1,362 @@
1
+ from typing import Any
2
+ import os
3
+ import sys
4
+ import logging
5
+ from datetime import datetime, UTC
6
+ from pathlib import Path
7
+
8
+ #: Fallback virtual-console width (columns) for a redirected/durable log sink.
9
+ #: Rich falls back to 80 columns when stdout is not a real terminal, which
10
+ #: hard-wraps long broker and exchange order identifiers (UUIDs) across
11
+ #: physical lines so they can no longer be grepped or copied as one token.
12
+ #: A wide virtual console keeps identifier-bearing lines on a single line.
13
+ DURABLE_LOG_WIDTH = 200
14
+
15
+ from .string import format as _format
16
+ from .. import lib
17
+
18
+ # Try to import rich, but don't fail if not available
19
+ try:
20
+ import rich
21
+ import rich.logging
22
+ except ImportError:
23
+ rich = None
24
+
25
+
26
+ __all__ = (
27
+ 'info', 'warning', 'error', 'logger', 'setup_security_file_log',
28
+ 'broker_debug', 'broker_info', 'broker_warning', 'broker_error', 'ohlcv_info',
29
+ 'sim_info',
30
+ )
31
+
32
+ _security_logger: logging.Logger | None = None
33
+
34
+ if os.environ.get("PYNE_NO_COLOR_LOG", "") == "1":
35
+ rich = None
36
+
37
+
38
+ def _resolve_log_console_width(is_tty: bool, columns_env: str | None) -> int | None:
39
+ """Pick the Rich console width for the log handler.
40
+
41
+ On an interactive terminal (``is_tty``) return ``None`` so Rich keeps
42
+ auto-detecting the real terminal width. When stdout is redirected to a
43
+ durable sink (file, pipe, systemd journal, Docker log) Rich would fall
44
+ back to 80 columns and fold long broker/order identifiers across lines;
45
+ return an explicit wide width so they stay searchable. An explicit
46
+ ``COLUMNS`` override always wins so a caller can widen or narrow the
47
+ transcript deliberately.
48
+
49
+ :param is_tty: Whether stdout is an interactive terminal.
50
+ :param columns_env: The raw ``COLUMNS`` environment value, if any.
51
+ :return: An explicit column count, or ``None`` to let Rich auto-detect.
52
+ """
53
+ if columns_env:
54
+ try:
55
+ width = int(columns_env)
56
+ except ValueError:
57
+ width = 0
58
+ if width > 0:
59
+ return width
60
+ if is_tty:
61
+ return None
62
+ return DURABLE_LOG_WIDTH
63
+
64
+
65
+ # noinspection PyProtectedMember
66
+ def _resolve_log_time(record: logging.LogRecord) -> datetime:
67
+ """Pick the timestamp + timezone for a log record.
68
+
69
+ Single source of truth for all three formatters (Rich, plain, security
70
+ file). Prefers Pine time (``lib._time``); falls back to the record's
71
+ wall clock when Pine time is unset (startup phase before the first bar).
72
+ Either way the result is timezone-aware: ``syminfo.timezone`` if set,
73
+ otherwise UTC — so the ``%z`` column is never blank.
74
+ """
75
+ tz_raw = getattr(getattr(lib, 'syminfo', None), 'timezone', None)
76
+ tz: str | None = tz_raw if isinstance(tz_raw, str) and tz_raw else None
77
+ epoch = lib._time / 1000 if lib._time else record.created
78
+ dt = datetime.fromtimestamp(epoch, UTC)
79
+ if tz is not None:
80
+ return dt.astimezone(lib._parse_timezone(tz))
81
+ return dt
82
+
83
+ # Custom RichHandler that uses Pine Script time and timezone
84
+ if rich:
85
+ class PineRichHandler(rich.logging.RichHandler):
86
+ """Custom RichHandler that formats time using syminfo.timezone"""
87
+
88
+ def render(self, *, record, traceback, message_renderable):
89
+ """Override render to use Pine Script time and timezone"""
90
+ from rich.text import Text
91
+ from rich.table import Table
92
+
93
+ log_time = _resolve_log_time(record)
94
+
95
+ # Format the time
96
+ path = Path(record.pathname).name
97
+ level = self.get_level_text(record)
98
+ time_format = None if self.formatter is None else self.formatter.datefmt
99
+
100
+ # Create custom log output with colored bar_index
101
+ if hasattr(lib, 'bar_index') and lib.bar_index is not None:
102
+ # Format the base time
103
+ time_str = log_time.strftime(time_format or "[%Y-%m-%d %H:%M:%S]")
104
+
105
+ # Create table for log output
106
+ output = Table.grid(padding=(0, 1))
107
+
108
+ # Add columns - no style for first two columns to allow Text styling
109
+ output.add_column() # Time column
110
+ output.add_column() # Bar index column
111
+ output.add_column(style="log.level", width=8) # Level column
112
+ output.add_column(style="log.message", overflow="fold") # Message column
113
+
114
+ # Build row with styled Text objects
115
+ row = [
116
+ Text(time_str, style="log.time"),
117
+ Text(f"bar: {lib.bar_index:6}", style="cyan"),
118
+ level,
119
+ message_renderable,
120
+ ]
121
+
122
+ output.add_row(*row)
123
+ return output
124
+ else:
125
+ # Standard rendering without bar_index
126
+ log_renderable = self._log_render(
127
+ self.console,
128
+ [message_renderable] if not traceback else [message_renderable, traceback],
129
+ log_time=log_time,
130
+ time_format=time_format,
131
+ level=level,
132
+ path=path,
133
+ line_no=record.lineno,
134
+ link_path=record.pathname if self.enable_link_path else None,
135
+ )
136
+ return log_renderable
137
+
138
+
139
+ # noinspection PyProtectedMember
140
+ class PineLogFormatter(logging.Formatter):
141
+ """Custom formatter that mimics Pine Script log format"""
142
+
143
+ def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str:
144
+ """Format the time using syminfo.timezone"""
145
+ dt = _resolve_log_time(record)
146
+
147
+ # Format the datetime
148
+ if datefmt:
149
+ time_str = dt.strftime(datefmt)
150
+ else:
151
+ time_str = dt.strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
152
+
153
+ # Add bar_index to the time string
154
+ if hasattr(lib, 'bar_index') and lib.bar_index is not None:
155
+ time_str = f"{time_str} bar: {lib.bar_index:6}"
156
+
157
+ return time_str
158
+
159
+ def format(self, record: logging.LogRecord) -> Any:
160
+ """Format log record in Pine style: [timestamp]: message"""
161
+ if record.args:
162
+ msg = _format(record.msg, *record.args)
163
+ else:
164
+ msg = str(record.msg)
165
+
166
+ record.args = ()
167
+
168
+ record.created = lib._time / 1000 if lib._time else record.created
169
+ if rich:
170
+ return msg
171
+
172
+ record.msg = msg
173
+ return super().format(record)
174
+
175
+
176
+ # Create logger
177
+ logger = logging.getLogger("pyne_core_logger")
178
+ # Remove existing handlers before adding new one
179
+ if logger.hasHandlers():
180
+ logger.handlers.clear()
181
+
182
+ logger.setLevel(logging.INFO)
183
+ if rich:
184
+ from rich.console import Console as _RichConsole
185
+
186
+ _stdout_is_tty = bool(getattr(sys.stdout, "isatty", lambda: False)())
187
+ _log_console_width = _resolve_log_console_width(
188
+ _stdout_is_tty, os.environ.get("COLUMNS"),
189
+ )
190
+ _log_console = (
191
+ None if _log_console_width is None
192
+ else _RichConsole(width=_log_console_width)
193
+ )
194
+ handler = PineRichHandler( # noqa
195
+ console=_log_console,
196
+ show_time=True,
197
+ show_level=True,
198
+ omit_repeated_times=False,
199
+ markup=False,
200
+ show_path=False,
201
+ )
202
+ else:
203
+ handler = logging.StreamHandler()
204
+ handler.setFormatter(PineLogFormatter(
205
+ "%(asctime)s %(levelname)-7s %(message)s",
206
+ datefmt="[%Y-%m-%d %H:%M:%S%z]")
207
+ )
208
+ logger.addHandler(handler)
209
+
210
+
211
+ def setup_security_file_log(log_path: str, context_label: str) -> None:
212
+ """
213
+ Set up file-based logging for a security process.
214
+
215
+ Called from security_process_main when PYNE_SECURITY_LOG is set.
216
+ The context_label (e.g. "AAPL 1D") is prepended to each log line.
217
+
218
+ :param log_path: Path to the log file
219
+ :param context_label: Security context identifier (symbol + timeframe)
220
+ """
221
+ global _security_logger
222
+ sec_logger = logging.getLogger(f"pyne_security_{context_label}")
223
+ sec_logger.setLevel(logging.DEBUG)
224
+ sec_logger.propagate = False
225
+ if sec_logger.hasHandlers():
226
+ sec_logger.handlers.clear()
227
+ fh = logging.FileHandler(log_path, mode='a', encoding='utf-8')
228
+ fh.setFormatter(SecurityFileFormatter(context_label))
229
+ sec_logger.addHandler(fh)
230
+ _security_logger = sec_logger
231
+
232
+
233
+ # noinspection PyProtectedMember
234
+ class SecurityFileFormatter(logging.Formatter):
235
+ """Formatter for security process file logs with context label and Pine time."""
236
+
237
+ def __init__(self, context_label: str):
238
+ super().__init__()
239
+ self.context_label = context_label
240
+
241
+ def format(self, record: logging.LogRecord) -> str:
242
+ if record.args:
243
+ msg = _format(record.msg, *record.args)
244
+ record.args = ()
245
+ else:
246
+ msg = str(record.msg)
247
+
248
+ dt = _resolve_log_time(record)
249
+ time_str = dt.strftime("%Y-%m-%d %H:%M:%S%z")
250
+ bar_str = f" bar: {lib.bar_index:6}" if hasattr(lib, 'bar_index') and lib.bar_index is not None else ""
251
+
252
+ return f"[{self.context_label}] [{time_str}]{bar_str} {record.levelname:<7} {msg}"
253
+
254
+
255
+ # noinspection PyPep8Naming,PyUnusedLocal
256
+ def info(formatString: str, *args: Any, **kwargs: Any) -> None:
257
+ """
258
+ Print an info message to the console.
259
+
260
+ :param formatString: Message format string
261
+ :param args: Arguments to format the message
262
+ :param kwargs: Additional arguments (unused)
263
+ """
264
+ # noinspection PyProtectedMember
265
+ if lib._lib_semaphore:
266
+ if _security_logger:
267
+ _security_logger.info(formatString, *args)
268
+ return
269
+ logger.info(formatString, *args)
270
+
271
+
272
+ # noinspection PyPep8Naming,PyUnusedLocal
273
+ def warning(formatString: str, *args: Any, **kwargs: Any) -> None:
274
+ """
275
+ Print a warning message to the console.
276
+
277
+ :param formatString: Message format string
278
+ :param args: Arguments to format the message
279
+ :param kwargs: Additional arguments (unused)
280
+ """
281
+ # noinspection PyProtectedMember
282
+ if lib._lib_semaphore:
283
+ if _security_logger:
284
+ _security_logger.warning(formatString, *args)
285
+ return
286
+ logger.warning(formatString, *args)
287
+
288
+
289
+ # noinspection PyPep8Naming,PyUnusedLocal
290
+ def error(formatString: str, *args: Any, **kwargs: Any) -> None:
291
+ """
292
+ Print an error message to the console.
293
+
294
+ :param formatString: Message format string
295
+ :param args: Arguments to format the message
296
+ :param kwargs: Additional arguments (unused)
297
+ """
298
+ # noinspection PyProtectedMember
299
+ if lib._lib_semaphore:
300
+ if _security_logger:
301
+ _security_logger.error(formatString, *args)
302
+ return
303
+ logger.error(formatString, *args)
304
+
305
+
306
+ #
307
+ # Internal tagged helpers
308
+ #
309
+ # These route non-Pine-script events (broker dispatch, OHLCV market data,
310
+ # any future category) through the same logger handler as ``info/warning/
311
+ # error`` above — uniform Pine-time + ``bar: N`` columns, same Rich
312
+ # Console (which the live CLI spinner shares). They pre-format ``%``-style
313
+ # placeholders client-side so call sites use idiomatic Python logging
314
+ # rather than Pine's ``string.format`` ``{0}/{1}`` numbered placeholders.
315
+ # All internal tagging lives here so the codebase has one central log
316
+ # module, not one per subsystem.
317
+ #
318
+
319
+ def _emit_tagged(level: int, prefix: str, message: str, args: tuple) -> None:
320
+ text = prefix + (message % args if args else message)
321
+ logger.log(level, text)
322
+
323
+
324
+ def broker_debug(message: str, *args: Any) -> None:
325
+ """Emit a DEBUG-level broker event (``[BROKER]`` tag).
326
+
327
+ For high-frequency or by-design-expected events (e.g. dropping a
328
+ redelivered duplicate fill) that must stay inspectable without flooding
329
+ the operator's INFO log.
330
+ """
331
+ _emit_tagged(logging.DEBUG, "[BROKER] ", message, args)
332
+
333
+
334
+ def broker_info(message: str, *args: Any) -> None:
335
+ """Emit an INFO-level broker event (``[BROKER]`` tag)."""
336
+ _emit_tagged(logging.INFO, "[BROKER] ", message, args)
337
+
338
+
339
+ def broker_warning(message: str, *args: Any) -> None:
340
+ """Emit a WARNING-level broker event (``[BROKER]`` tag)."""
341
+ _emit_tagged(logging.WARNING, "[BROKER] ", message, args)
342
+
343
+
344
+ def broker_error(message: str, *args: Any) -> None:
345
+ """Emit an ERROR-level broker event (``[BROKER]`` tag)."""
346
+ _emit_tagged(logging.ERROR, "[BROKER] ", message, args)
347
+
348
+
349
+ def ohlcv_info(message: str, *args: Any) -> None:
350
+ """Emit an INFO-level market-data line (``[OHLCV]`` tag)."""
351
+ _emit_tagged(logging.INFO, "[OHLCV] ", message, args)
352
+
353
+
354
+ def sim_info(message: str, *args: Any) -> None:
355
+ """Emit an INFO-level paper-trading event (``[SIM]`` tag).
356
+
357
+ The simulator counterpart to ``broker_info``: in ``--live`` mode without
358
+ ``--broker`` the :class:`SimPosition` fills orders locally, so there is no
359
+ ``OrderSyncEngine`` to narrate entries and exits. This gives the operator
360
+ the same per-fill visibility for paper trading.
361
+ """
362
+ _emit_tagged(logging.INFO, "[SIM] ", message, args)
pynecore/lib/map.py ADDED
@@ -0,0 +1,150 @@
1
+ from typing import TypeVar, Any
2
+ # Canonical nan map key (the interned na_float). A nan never equals itself, so
3
+ # two different nan float objects miss each other in a dict; the CPython lookup
4
+ # identity fast path DOES hit for the same object though. Canonicalizing every
5
+ # nan key to this interned constant makes an na key storable and retrievable
6
+ # (TV-verified: map.get / contains find a float(na) key after map.put with one).
7
+ # Imported underscored so it stays out of the lib module-property registry.
8
+ from pynecore.types.na import NA, na_float as _NAN_KEY
9
+
10
+ TKey = TypeVar('TKey')
11
+ TValue = TypeVar('TValue')
12
+
13
+
14
+ # noinspection PyShadowingBuiltins
15
+ def clear(id: dict):
16
+ """
17
+ Clear the map.
18
+
19
+ :param id: The map to clear.
20
+ """
21
+ id.clear()
22
+
23
+
24
+ # noinspection PyShadowingBuiltins
25
+ def contains(id: dict, key: Any) -> bool:
26
+ """
27
+ Check if the map contains a key.
28
+
29
+ :param id: The map to check.
30
+ :param key: The key to check for.
31
+ """
32
+ if key != key:
33
+ key = _NAN_KEY
34
+ return key in id
35
+
36
+
37
+ # noinspection PyShadowingBuiltins
38
+ def copy(id: dict[TKey, TValue]) -> dict[TKey, TValue]:
39
+ """
40
+ Copy the map.
41
+
42
+ :param id: The map to copy.
43
+ """
44
+ return id.copy()
45
+
46
+
47
+ # noinspection PyShadowingBuiltins
48
+ def get(id: dict[TKey, TValue], key: TKey) -> TValue:
49
+ """
50
+ Get the value of a key in the map.
51
+
52
+ :param id: The map to get the value from.
53
+ :param key: The key to get the value from.
54
+ :return: The value associated with the key, or na when the key is absent.
55
+ """
56
+ if key != key:
57
+ key = _NAN_KEY
58
+ try:
59
+ return id[key]
60
+ except KeyError:
61
+ return NA(None) # value type is unknown at run-time (see ``remove``)
62
+
63
+
64
+ # noinspection PyShadowingBuiltins
65
+ def keys(id: dict[TKey, TValue]) -> list[TKey]:
66
+ """
67
+ Get the keys of the map.
68
+
69
+ :param id: The map to get the keys from.
70
+ """
71
+ return list(id.keys())
72
+
73
+
74
+ def new() -> dict:
75
+ """
76
+ Create a new map object.
77
+
78
+ :return: A new map object.
79
+ """
80
+ return {}
81
+
82
+
83
+ # noinspection PyShadowingBuiltins
84
+ def put(id: dict, key: Any, value: TValue) -> TValue:
85
+ """
86
+ Put a key-value pair in the map.
87
+
88
+ :param id: The map to put the key-value pair in.
89
+ :param key: The key to put in the map.
90
+ :param value: The value to put in the map.
91
+ :return: The value that was previously in the map.
92
+ """
93
+ if key != key:
94
+ key = _NAN_KEY
95
+ try:
96
+ old_value = id[key]
97
+ except KeyError:
98
+ old_value = NA(type(value))
99
+ id[key] = value
100
+ return old_value
101
+
102
+
103
+ # noinspection PyShadowingBuiltins
104
+ def put_all(id: dict[TKey, TValue], other: dict[TKey, TValue]):
105
+ """
106
+ Put all the key-value pairs from another map into this map.
107
+
108
+ :param id: The map to put the key-value pairs in.
109
+ :param other: The map to put the key-value pairs from.
110
+ """
111
+ id.update(other)
112
+
113
+
114
+ # noinspection PyShadowingBuiltins
115
+ def remove(id: dict[TKey, TValue], key: TKey) -> TValue:
116
+ """
117
+ Remove a key-value pair from the map.
118
+
119
+ :param id: The map to remove the key-value pair from.
120
+ :param key: The key to remove from the map.
121
+ :return: The value that was removed from the map.
122
+ """
123
+ if key != key:
124
+ key = _NAN_KEY
125
+ try:
126
+ return id.pop(key)
127
+ except KeyError:
128
+ return NA(None) # We don't know the type of the map values at run-time
129
+
130
+
131
+ # noinspection PyShadowingBuiltins
132
+ def size(id: dict[TKey, TValue]) -> int:
133
+ """
134
+ Get the size of the map.
135
+
136
+ :param id: The map to get the size of.
137
+ :return: The size of the map.
138
+ """
139
+ return len(id)
140
+
141
+
142
+ # noinspection PyShadowingBuiltins
143
+ def values(id: dict[TKey, TValue]) -> list[TValue]:
144
+ """
145
+ Get the values of the map.
146
+
147
+ :param id: The map to get the values from.
148
+ :return: A list of the values in the map.
149
+ """
150
+ return list(id.values())