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
@@ -0,0 +1,417 @@
1
+ from __future__ import annotations
2
+ from typing import TypeVar, Generic, Iterator, Callable, cast, overload
3
+
4
+ from types import ModuleType
5
+
6
+ # noinspection PyProtectedMember
7
+ from ..types.na import NA
8
+
9
+ __all__ = ['SeriesImpl', 'inline_series']
10
+
11
+ T = TypeVar('T')
12
+
13
+ # Interned na sentinel of untyped series (element type unknown at transform
14
+ # time). Hybrid guards (``isinstance(x, NA) or x != x``) treat it and the
15
+ # native nan as the same na.
16
+ _NA_UNTYPED: NA = NA(T)
17
+
18
+
19
+ class SeriesImpl(Generic[T]):
20
+ """
21
+ A dynamic circular buffer that behaves similarly to Pine Script's "Series".
22
+ Every docstring and comment is in English, as requested.
23
+
24
+ Note: max_bars_back means the maximum number of older bars that can be indexed.
25
+ Therefore, the actual buffer capacity is (max_bars_back + 1).
26
+ """
27
+
28
+ __slots__ = ('_buffer',
29
+ '_max_bars_back', '_max_bars_back_set',
30
+ '_capacity', '_write_pos', '_size',
31
+ '_last_bar_index', '_na')
32
+
33
+ DEFAULT_MAX_BARS_BACK = 500 # This can be set globally by indicator or strategy commands
34
+ MAXIMUM_MAX_BARS_BACK = 5000 # This is the maximum allowed value
35
+
36
+ _lib: ModuleType = None # Placeholder for the lib module
37
+
38
+ # noinspection PyMissingConstructor
39
+ def __init__(self, max_bars_back: int | None = None, na_value: T | None = None):
40
+ """
41
+ :param max_bars_back: Optional initial capacity for historical lookback.
42
+ If not provided, DEFAULT_MAX_BARS_BACK is used.
43
+ The actual buffer capacity will be (max_bars_back + 1).
44
+ :param na_value: The na sentinel returned for out-of-range reads.
45
+ Float series pass the native nan here so a warmup /
46
+ out-of-history read behaves exactly like every other
47
+ float na; untyped series keep the interned NA object.
48
+ """
49
+ # Importing the lib module here to avoid circular imports
50
+ if not SeriesImpl._lib:
51
+ from .. import lib
52
+ SeriesImpl._lib = lib
53
+
54
+ self._na: T | NA[T] = _NA_UNTYPED if na_value is None else na_value
55
+
56
+ self._max_bars_back = max_bars_back or self.DEFAULT_MAX_BARS_BACK
57
+ self._max_bars_back_set = max_bars_back or 0
58
+ # Internal capacity is max_bars_back + 1 (for current candle + historical bars)
59
+ self._capacity = self._max_bars_back + 1
60
+
61
+ # Pre-allocated buffer for better performance. The None placeholders
62
+ # only ever fill the unused tail (positions >= _size are never read),
63
+ # so the element type can omit None and reads stay cast-free.
64
+ self._buffer: list[T | NA[T]] = cast('list[T | NA[T]]', [None] * self._capacity)
65
+
66
+ # The next logical write position in a circular manner.
67
+ self._write_pos = 0
68
+
69
+ # The current number of valid elements in the buffer (<= self._capacity).
70
+ self._size = 0
71
+
72
+ # The last bar index that was accessed
73
+ self._last_bar_index = -1
74
+
75
+ @property
76
+ def max_bars_back(self) -> int:
77
+ """
78
+ Returns the current max_bars_back, i.e. how many historical bars can be indexed.
79
+ """
80
+ return self._max_bars_back
81
+
82
+ @max_bars_back.setter
83
+ def max_bars_back(self, new_max_bars_back: int) -> None:
84
+ """
85
+ Resizes the circular buffer capacity to (new_value + 1).
86
+ - If increased, old items are preserved (up to the old _size), and
87
+ the buffer is linearized.
88
+ - If decreased, only the most recent 'new_value + 1' items are kept.
89
+
90
+ :param new_max_bars_back: The new 'max_bars_back' value.
91
+ :raises ValueError: If new_value <= 0.
92
+ """
93
+ if new_max_bars_back <= 0:
94
+ raise ValueError("The max_bars_back must be a positive integer!")
95
+ if new_max_bars_back > SeriesImpl.MAXIMUM_MAX_BARS_BACK:
96
+ raise ValueError(f"The max_bars_back cannot exceed {self.MAXIMUM_MAX_BARS_BACK}!")
97
+
98
+ if new_max_bars_back == self._max_bars_back:
99
+ return # No change
100
+
101
+ old_size = self._size
102
+ old_write_pos = self._write_pos
103
+
104
+ new_capacity = new_max_bars_back + 1
105
+ if new_capacity > old_size == old_write_pos:
106
+ # The buffer is not yet full, and the write position is at the end.
107
+ # We can just modify the max_bars_back and capacity, and extend the buffer
108
+ self._max_bars_back = new_max_bars_back
109
+ self._max_bars_back_set = new_max_bars_back
110
+ self._capacity = new_capacity
111
+ # Extend pre-allocated buffer with None placeholders (unused tail)
112
+ self._buffer.extend(cast('list[T | NA[T]]',
113
+ [None] * (new_capacity - len(self._buffer))))
114
+ return
115
+
116
+ old_buffer = self._buffer
117
+ old_capacity = self._capacity
118
+
119
+ # Number of items to keep: either all old items or new_capacity, whichever is smaller.
120
+ items_to_keep = min(old_size, new_capacity)
121
+
122
+ # Build a new pre-allocated buffer (None placeholders in the unused tail).
123
+ new_buffer: list[T | NA[T]] = cast('list[T | NA[T]]', [None] * new_capacity)
124
+
125
+ if items_to_keep > 0 and old_buffer:
126
+ # The newest item is at (old_write_pos - 1) in a circular manner.
127
+ # We'll copy the most recent 'items_to_keep' from the old buffer in linear order.
128
+ start_idx = (old_write_pos - items_to_keep) % old_capacity
129
+
130
+ for i in range(items_to_keep):
131
+ src_idx = (start_idx + i) % old_capacity
132
+ new_buffer[i] = old_buffer[src_idx]
133
+
134
+ # Update references
135
+ self._buffer = new_buffer
136
+ self._max_bars_back = new_max_bars_back
137
+ self._max_bars_back_set = new_max_bars_back
138
+ self._capacity = new_capacity
139
+ self._size = items_to_keep
140
+ self._write_pos = items_to_keep # The next free slot is after the last copied item
141
+
142
+ def add(self, value: T | NA[T]) -> T | NA[T]:
143
+ """
144
+ Adds a new candle (data point) to the buffer.
145
+ - If the buffer is not yet at full capacity (size < capacity), it appends the new value.
146
+ - Otherwise, it overwrites the oldest data in a circular manner.
147
+ :param value: The new data to be added.
148
+ :return: The same value that was added (for chaining or inline usage).
149
+ """
150
+ # Optimize attribute lookup by using local variable
151
+ lib = SeriesImpl._lib
152
+
153
+ # Set data instead of adding a new one if the bar index is the same
154
+ if self._last_bar_index == lib.bar_index:
155
+ return self.set(value)
156
+
157
+ if self._size < self._capacity:
158
+ # The buffer is not yet full - use direct indexing to pre-allocated buffer
159
+ self._buffer[self._write_pos] = value
160
+ self._size += 1
161
+ self._write_pos += 1
162
+ else:
163
+ # The buffer is full: overwrite in circular fashion
164
+ pos = self._write_pos
165
+ if pos >= self._capacity:
166
+ pos = 0
167
+ self._write_pos = 1
168
+ else:
169
+ self._write_pos += 1
170
+ self._buffer[pos] = value
171
+
172
+ # Store the last bar index to prevent adding more than one value per bar
173
+ self._last_bar_index = lib.bar_index
174
+
175
+ return value
176
+
177
+ def set(self, value: T | NA[T]) -> T | NA[T]:
178
+ """
179
+ Overwrites the most recently added (current) candle value.
180
+ If there is no data yet, returns na.
181
+ :param value: The new value for the current candle.
182
+ :return: The value that was set, or na if buffer is empty.
183
+ """
184
+ if self._size == 0:
185
+ return self._na
186
+
187
+ pos = self._write_pos - 1
188
+ if pos < 0:
189
+ pos += self._capacity
190
+ self._buffer[pos] = value
191
+ return value
192
+
193
+ def _snapshot(self) -> tuple[list[T | NA[T]], int, int, int, int, int, int]:
194
+ """Capture the full buffer state for a rollback baseline.
195
+
196
+ Used by the live ``request.security_lower_tf`` LTF baseline to roll a
197
+ series back when a reordered feed forces a replay that re-runs an
198
+ *earlier* ``bar_index`` after a later one already ran (see
199
+ :class:`~pynecore.core.live_ltf_collector.LiveLtfCollector`). A cursor-only
200
+ snapshot is not enough: at capacity a stray :meth:`add` overwrites the
201
+ oldest element circularly, which the cursor alone cannot reconstruct — so
202
+ the buffer contents are copied too. Cheap at live cadence (one copy per
203
+ confirmed intrabar), never used on the warmup forward-only path.
204
+
205
+ :return: An opaque tuple consumed only by :meth:`_restore`.
206
+ """
207
+ return (self._buffer.copy(), self._size, self._write_pos, self._last_bar_index,
208
+ self._max_bars_back, self._max_bars_back_set, self._capacity)
209
+
210
+ def _restore(self, snapshot: tuple[list[T | NA[T]], int, int, int, int, int, int]) -> None:
211
+ """Restore the buffer state captured by :meth:`_snapshot` (in place).
212
+
213
+ The same snapshot may be restored repeatedly (one save, many replay
214
+ re-runs), so the saved buffer is copied back rather than aliased.
215
+
216
+ :param snapshot: A value returned by :meth:`_snapshot`.
217
+ """
218
+ (buffer, self._size, self._write_pos, self._last_bar_index,
219
+ self._max_bars_back, self._max_bars_back_set, self._capacity) = snapshot
220
+ self._buffer = buffer.copy()
221
+
222
+ @overload
223
+ def __getitem__(self, key: int) -> T | NA[T]:
224
+ ...
225
+
226
+ @overload
227
+ def __getitem__(self, key: slice) -> ReadOnlySeriesView[T]:
228
+ ...
229
+
230
+ def __getitem__(self, key: int | slice) -> T | NA[T] | ReadOnlySeriesView[T]:
231
+ """
232
+ Get item(s) using Pine indexing with slice support.
233
+
234
+ Pine semantics: subscripting with `na` returns the current bar's value
235
+ (offset 0) — TradingView's history-referencing operator treats an `na`
236
+ offset as 0. An out-of-range integer (negative = forward of the current
237
+ bar, positive >= size = before available history) returns `na` rather
238
+ than raising.
239
+
240
+ :param key: Integer index, NA, or slice
241
+ :return: Single value for integer index, ReadOnlySeriesView for slice
242
+ :raises TypeError: If key is not int, NA, or slice
243
+ """
244
+ # Pine: series[na] -> series[0] (an na offset is treated as the current
245
+ # bar). Coerce to 0 here, before the int(key) path, since int(NA) raises;
246
+ # the bounds check below then returns na only for a genuinely empty series.
247
+ if isinstance(key, NA):
248
+ key = 0
249
+
250
+ if isinstance(key, float):
251
+ # A native nan offset is an na offset -> current bar (int(nan) raises)
252
+ key = 0 if key != key else int(key)
253
+
254
+ if isinstance(key, int):
255
+ # Pine: out-of-range subscript -> na (covers both negative and
256
+ # positive past-end indices).
257
+ if key < 0 or key >= self._size:
258
+ return self._na
259
+ pos = self._write_pos - 1 - key
260
+ if pos < 0:
261
+ pos += self._capacity
262
+ return self._buffer[pos]
263
+
264
+ elif isinstance(key, slice):
265
+ # Handle slice notation
266
+ start = 0 if key.start is None else key.start
267
+ stop = self._size if key.stop is None else key.stop
268
+
269
+ if start < 0 or stop < 0:
270
+ raise IndexError("Negative indices not supported in slice!")
271
+
272
+ if key.step is not None and key.step != 1:
273
+ raise ValueError("Step value not supported in slice!")
274
+
275
+ if stop > self._size:
276
+ raise IndexError("Slice stop index out of range!")
277
+
278
+ # Ensure start <= stop
279
+ if start > stop:
280
+ start = stop
281
+
282
+ return ReadOnlySeriesView[T](
283
+ self._buffer,
284
+ self._capacity,
285
+ self._write_pos,
286
+ self._size,
287
+ start,
288
+ stop
289
+ )
290
+
291
+ raise TypeError("Series indices must be integers or slices") # noqa
292
+
293
+ def __len__(self) -> int:
294
+ """
295
+ Returns the number of valid data points in the buffer (<= capacity).
296
+ """
297
+ return self._size
298
+
299
+ def __iter__(self) -> Iterator[T | NA[T]]:
300
+ """
301
+ Iterate valid items from newest to oldest (Pine index order).
302
+
303
+ Without an explicit __iter__, ``iter(series)`` would fall back to the
304
+ sequence protocol, and since ``__getitem__`` returns na for every
305
+ out-of-range index (never IndexError), that iteration never terminates.
306
+ """
307
+ for i in range(self._size):
308
+ pos = self._write_pos - 1 - i
309
+ if pos < 0:
310
+ pos += self._capacity
311
+ yield self._buffer[pos]
312
+
313
+
314
+ class ReadOnlySeriesView(Generic[T]):
315
+ """
316
+ A read-only view for a circular buffer slice that follows Pine-like indexing.
317
+ Supports positive slice notation [start:stop] where start and stop refer to
318
+ bar indices from the most recent bar.
319
+ """
320
+ __slots__ = ('_buffer', '_capacity', '_write_pos', '_size', '_start', '_stop')
321
+
322
+ def __init__(self, buffer: list[T | NA[T]], capacity: int, write_pos: int, size: int,
323
+ start: int, stop: int) -> None:
324
+ """
325
+ Initialize the view.
326
+
327
+ :param buffer: The source buffer
328
+ :param capacity: The capacity of the buffer
329
+ :param write_pos: The current write position
330
+ :param size: The number of valid items
331
+ :param start: Start index (inclusive)
332
+ :param stop: Stop index (exclusive)
333
+ """
334
+ self._buffer = buffer
335
+ self._capacity = capacity
336
+ self._write_pos = write_pos
337
+ self._size = size
338
+ self._start = start
339
+ self._stop = min(stop, size)
340
+
341
+ def __getitem__(self, idx: int) -> T | NA[T]:
342
+ """Get item using Pine indexing"""
343
+ if not isinstance(idx, int):
344
+ raise TypeError("Only integer indexing is supported") # noqa
345
+ if idx < 0:
346
+ raise IndexError("Negative indices not supported")
347
+ if idx >= len(self):
348
+ raise IndexError("Index out of range")
349
+
350
+ actual_idx = idx + self._start
351
+ pos = self._write_pos - 1 - actual_idx
352
+ if pos < 0:
353
+ pos += self._capacity
354
+ return self._buffer[pos]
355
+
356
+ def __len__(self) -> int:
357
+ """Get length of the view"""
358
+ return self._stop - self._start
359
+
360
+ def __iter__(self) -> Iterator[T | NA[T]]:
361
+ """Iterate through items from newest to oldest"""
362
+ for i in range(self._start, self._stop):
363
+ pos = self._write_pos - 1 - i
364
+ if pos < 0:
365
+ pos += self._capacity
366
+ yield self._buffer[pos]
367
+
368
+ def __repr__(self) -> str:
369
+ """Get string representation"""
370
+ return f"ReadOnlySeriesView({list(self)})"
371
+
372
+
373
+ def _inline_series_instance() -> Callable[..., SeriesImpl]:
374
+ """One ``inline_series`` instance: a closure over its own series buffer.
375
+
376
+ The slot transform routes ``inline_series`` call sites on the uniform
377
+ path, so ``__bind_any__`` anchors one of these per call site (and one per
378
+ iteration at loop sites) through the ``__pyne_bind__`` factory — that is
379
+ what keeps two ``expr[n]`` rewrites in one scope independent (issue #61).
380
+
381
+ :return: The bound instance with the public ``inline_series`` signature.
382
+ """
383
+ series: SeriesImpl = SeriesImpl()
384
+
385
+ def bound(value: T, idx: int) -> SeriesImpl[T]:
386
+ if isinstance(idx, float):
387
+ idx = int(idx)
388
+ series.add(value)
389
+ return cast(SeriesImpl[T], series[idx])
390
+
391
+ return bound
392
+
393
+
394
+ _inline_series_shared: SeriesImpl = SeriesImpl()
395
+
396
+
397
+ def inline_series(value: T, idx: int) -> SeriesImpl[T]:
398
+ """
399
+ Inline series creation
400
+ It is mainly for compiled codes
401
+
402
+ Calling it directly (no anchor — non-transformed code) uses one shared
403
+ module-lifetime buffer, the same semantics the legacy module-global
404
+ series gave such calls; anchored call sites get independent buffers from
405
+ ``__pyne_bind__``.
406
+
407
+ :param value: The value to store
408
+ :param idx: The index, 0 is the last, 1 is the second last and so on
409
+ :return:
410
+ """
411
+ if isinstance(idx, float):
412
+ idx = int(idx)
413
+ _inline_series_shared.add(value)
414
+ return cast(SeriesImpl[T], _inline_series_shared[idx])
415
+
416
+
417
+ setattr(inline_series, '__pyne_bind__', _inline_series_instance)