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.
- opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
- opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
- opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
- opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
- opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
- pynecore/__init__.py +6 -0
- pynecore/cli/__init__.py +2 -0
- pynecore/cli/app.py +238 -0
- pynecore/cli/commands/__init__.py +343 -0
- pynecore/cli/commands/benchmark.py +186 -0
- pynecore/cli/commands/compile.py +198 -0
- pynecore/cli/commands/data.py +857 -0
- pynecore/cli/commands/debug.py +63 -0
- pynecore/cli/commands/optimize.py +956 -0
- pynecore/cli/commands/plugin.py +242 -0
- pynecore/cli/commands/run.py +2006 -0
- pynecore/cli/pluggable.py +132 -0
- pynecore/cli/utils/__init__.py +0 -0
- pynecore/cli/utils/api_error_handler.py +168 -0
- pynecore/cli/utils/broker_picker.py +330 -0
- pynecore/cli/utils/error_hook.py +28 -0
- pynecore/cli/utils/keyreader.py +178 -0
- pynecore/cli/utils/provider_picker.py +19 -0
- pynecore/cli/utils/symbol_browser.py +1149 -0
- pynecore/core/__init__.py +0 -0
- pynecore/core/aggregator.py +257 -0
- pynecore/core/bar_magnifier.py +168 -0
- pynecore/core/broker/__init__.py +64 -0
- pynecore/core/broker/defaults.py +113 -0
- pynecore/core/broker/disappearance.py +927 -0
- pynecore/core/broker/emulator.py +345 -0
- pynecore/core/broker/exceptions.py +346 -0
- pynecore/core/broker/idempotency.py +401 -0
- pynecore/core/broker/intent_builder.py +334 -0
- pynecore/core/broker/journal.py +1785 -0
- pynecore/core/broker/models.py +1600 -0
- pynecore/core/broker/native_failsafe_manager.py +1436 -0
- pynecore/core/broker/one_way_emulator.py +1128 -0
- pynecore/core/broker/position.py +787 -0
- pynecore/core/broker/run_identity.py +126 -0
- pynecore/core/broker/software_entry_stop_engine.py +351 -0
- pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
- pynecore/core/broker/spot_inventory.py +1327 -0
- pynecore/core/broker/storage.py +2655 -0
- pynecore/core/broker/store_helpers.py +2161 -0
- pynecore/core/broker/sync_engine.py +16070 -0
- pynecore/core/broker/validation.py +382 -0
- pynecore/core/class_property.py +7 -0
- pynecore/core/config.py +392 -0
- pynecore/core/csv_file.py +547 -0
- pynecore/core/currency.py +262 -0
- pynecore/core/data_converter.py +1002 -0
- pynecore/core/datetime.py +296 -0
- pynecore/core/download_info.py +71 -0
- pynecore/core/download_runner.py +274 -0
- pynecore/core/htf_aggregator.py +181 -0
- pynecore/core/import_hook.py +358 -0
- pynecore/core/instance_state.py +494 -0
- pynecore/core/live_ltf_collector.py +442 -0
- pynecore/core/live_ltf_window.py +189 -0
- pynecore/core/live_runner.py +1347 -0
- pynecore/core/module_property.py +26 -0
- pynecore/core/ohlcv_file.py +1888 -0
- pynecore/core/overload.py +371 -0
- pynecore/core/pine_cast.py +113 -0
- pynecore/core/pine_export.py +95 -0
- pynecore/core/pine_method.py +244 -0
- pynecore/core/pine_range.py +86 -0
- pynecore/core/pine_udt.py +69 -0
- pynecore/core/plugin/__init__.py +394 -0
- pynecore/core/plugin/broker.py +781 -0
- pynecore/core/plugin/cli.py +96 -0
- pynecore/core/plugin/live_provider.py +208 -0
- pynecore/core/plugin/provider.py +331 -0
- pynecore/core/provider_string.py +148 -0
- pynecore/core/random.py +40 -0
- pynecore/core/resampler.py +686 -0
- pynecore/core/safe_convert.py +64 -0
- pynecore/core/script.py +1011 -0
- pynecore/core/script_runner.py +3202 -0
- pynecore/core/security.py +1749 -0
- pynecore/core/security_process.py +1253 -0
- pynecore/core/security_shm.py +456 -0
- pynecore/core/series.py +417 -0
- pynecore/core/strategy_stats.py +669 -0
- pynecore/core/symbol_map.py +134 -0
- pynecore/core/syminfo.py +505 -0
- pynecore/core/viz.py +591 -0
- pynecore/lib/__init__.py +1771 -0
- pynecore/lib/_fixnan.py +32 -0
- pynecore/lib/_math_stateful.py +202 -0
- pynecore/lib/_timeframe_change.py +101 -0
- pynecore/lib/adjustment.py +6 -0
- pynecore/lib/alert.py +39 -0
- pynecore/lib/alert.pyi +14 -0
- pynecore/lib/array.py +1051 -0
- pynecore/lib/barmerge.py +60 -0
- pynecore/lib/barstate.py +30 -0
- pynecore/lib/box.py +415 -0
- pynecore/lib/chart.py +128 -0
- pynecore/lib/color.py +152 -0
- pynecore/lib/color.pyi +50 -0
- pynecore/lib/currency.py +62 -0
- pynecore/lib/dayofweek.py +36 -0
- pynecore/lib/dayofweek.pyi +18 -0
- pynecore/lib/display.py +8 -0
- pynecore/lib/dividends.py +9 -0
- pynecore/lib/earnings.py +11 -0
- pynecore/lib/extend.py +6 -0
- pynecore/lib/font.py +5 -0
- pynecore/lib/footprint.py +79 -0
- pynecore/lib/format.py +11 -0
- pynecore/lib/hline.py +67 -0
- pynecore/lib/hline.pyi +24 -0
- pynecore/lib/label.py +409 -0
- pynecore/lib/line.py +433 -0
- pynecore/lib/linefill.py +93 -0
- pynecore/lib/location.py +11 -0
- pynecore/lib/log.py +362 -0
- pynecore/lib/map.py +150 -0
- pynecore/lib/math.py +385 -0
- pynecore/lib/matrix.py +708 -0
- pynecore/lib/order.py +8 -0
- pynecore/lib/pivotpointtype.py +8 -0
- pynecore/lib/plot.py +95 -0
- pynecore/lib/plot.pyi +33 -0
- pynecore/lib/polyline.py +91 -0
- pynecore/lib/position.py +15 -0
- pynecore/lib/request.py +281 -0
- pynecore/lib/runtime.py +5 -0
- pynecore/lib/scale.py +9 -0
- pynecore/lib/session.py +267 -0
- pynecore/lib/session.pyi +12 -0
- pynecore/lib/shape.py +18 -0
- pynecore/lib/size.py +12 -0
- pynecore/lib/splits.py +4 -0
- pynecore/lib/strategy/__init__.py +4778 -0
- pynecore/lib/strategy/closedtrades.py +347 -0
- pynecore/lib/strategy/closedtrades.pyi +53 -0
- pynecore/lib/strategy/commission.py +9 -0
- pynecore/lib/strategy/direction.py +9 -0
- pynecore/lib/strategy/oca.py +13 -0
- pynecore/lib/strategy/opentrades.py +281 -0
- pynecore/lib/strategy/opentrades.pyi +49 -0
- pynecore/lib/strategy/risk.py +109 -0
- pynecore/lib/string.py +649 -0
- pynecore/lib/syminfo.py +84 -0
- pynecore/lib/ta.py +2230 -0
- pynecore/lib/table.py +290 -0
- pynecore/lib/text.py +17 -0
- pynecore/lib/ticker.py +207 -0
- pynecore/lib/timeframe.py +293 -0
- pynecore/lib/volume_row.py +67 -0
- pynecore/lib/xloc.py +4 -0
- pynecore/lib/yloc.py +5 -0
- pynecore/providers/__init__.py +0 -0
- pynecore/providers/ccxt.py +664 -0
- pynecore/providers/replay.py +187 -0
- pynecore/pynesys/__init__.py +0 -0
- pynecore/pynesys/api.py +498 -0
- pynecore/pynesys/compiler.py +112 -0
- pynecore/standalone.py +99 -0
- pynecore/testing/__init__.py +1 -0
- pynecore/testing/broker_lab/__init__.py +41 -0
- pynecore/testing/broker_lab/__main__.py +5 -0
- pynecore/testing/broker_lab/cli.py +87 -0
- pynecore/testing/broker_lab/generate.py +47 -0
- pynecore/testing/broker_lab/model.py +84 -0
- pynecore/testing/broker_lab/reference.py +645 -0
- pynecore/testing/broker_lab/runner.py +372 -0
- pynecore/testing/broker_lab/scheduler.py +50 -0
- pynecore/testing/broker_lab/subprocess.py +73 -0
- pynecore/transformers/__init__.py +0 -0
- pynecore/transformers/builtin_shadow.py +136 -0
- pynecore/transformers/closure_arguments_transformer.py +428 -0
- pynecore/transformers/display_rewrite.py +140 -0
- pynecore/transformers/dynamic_default.py +147 -0
- pynecore/transformers/function_isolation.py +757 -0
- pynecore/transformers/import_lifter.py +61 -0
- pynecore/transformers/import_normalizer.py +328 -0
- pynecore/transformers/inline_series_hoist.py +178 -0
- pynecore/transformers/input_transformer.py +175 -0
- pynecore/transformers/lib_series.py +201 -0
- pynecore/transformers/locations.py +70 -0
- pynecore/transformers/module_properties.json +3387 -0
- pynecore/transformers/module_property.py +221 -0
- pynecore/transformers/ne_guard.py +70 -0
- pynecore/transformers/persistent.py +320 -0
- pynecore/transformers/persistent_series.py +76 -0
- pynecore/transformers/safe_convert_transformer.py +97 -0
- pynecore/transformers/safe_division_transformer.py +95 -0
- pynecore/transformers/script_requirements.py +308 -0
- pynecore/transformers/security.py +752 -0
- pynecore/transformers/security_instantiation.py +274 -0
- pynecore/transformers/series.py +275 -0
- pynecore/transformers/slot_layout.py +381 -0
- pynecore/transformers/type_checking_stripper.py +25 -0
- pynecore/transformers/unused_series_detector.py +267 -0
- pynecore/types/__init__.py +21 -0
- pynecore/types/alert.py +5 -0
- pynecore/types/barmerge.py +5 -0
- pynecore/types/base.py +39 -0
- pynecore/types/box.py +37 -0
- pynecore/types/chart.py +17 -0
- pynecore/types/color.py +107 -0
- pynecore/types/currency.py +5 -0
- pynecore/types/datetime.py +6 -0
- pynecore/types/display.py +5 -0
- pynecore/types/dividends.py +5 -0
- pynecore/types/earnings.py +5 -0
- pynecore/types/extend.py +5 -0
- pynecore/types/font.py +5 -0
- pynecore/types/footprint.py +41 -0
- pynecore/types/format.py +5 -0
- pynecore/types/hline.py +24 -0
- pynecore/types/ib_persistent.py +8 -0
- pynecore/types/ib_persistent.pyi +10 -0
- pynecore/types/label.py +35 -0
- pynecore/types/line.py +32 -0
- pynecore/types/linefill.py +13 -0
- pynecore/types/location.py +5 -0
- pynecore/types/matrix.py +999 -0
- pynecore/types/na.py +237 -0
- pynecore/types/na.pyi +83 -0
- pynecore/types/ohlcv.py +12 -0
- pynecore/types/order.py +5 -0
- pynecore/types/persistent.py +8 -0
- pynecore/types/persistent.pyi +13 -0
- pynecore/types/pine_types.py +11 -0
- pynecore/types/pine_types.pyi +15 -0
- pynecore/types/pivotpointtype.py +5 -0
- pynecore/types/plot.py +12 -0
- pynecore/types/plot_meta.py +60 -0
- pynecore/types/polyline.py +40 -0
- pynecore/types/position.py +5 -0
- pynecore/types/scale.py +5 -0
- pynecore/types/script_type.py +15 -0
- pynecore/types/series.py +23 -0
- pynecore/types/series.pyi +19 -0
- pynecore/types/session.py +35 -0
- pynecore/types/shape.py +5 -0
- pynecore/types/size.py +5 -0
- pynecore/types/source.py +33 -0
- pynecore/types/splits.py +5 -0
- pynecore/types/strategy.py +45 -0
- pynecore/types/table.py +87 -0
- pynecore/types/text.py +13 -0
- pynecore/types/type_checker.py +7 -0
- pynecore/types/type_checker.pyi +48 -0
- pynecore/types/volume_row.py +36 -0
- pynecore/types/weekdays.py +11 -0
- pynecore/types/xloc.py +5 -0
- pynecore/types/yloc.py +5 -0
- pynecore/utils/__init__.py +0 -0
- pynecore/utils/file_utils.py +50 -0
- pynecore/utils/rich/__init__.py +0 -0
- pynecore/utils/rich/date_column.py +25 -0
- pynecore/utils/sequence_view.py +92 -0
- pynecore/utils/stdlib_checker.py +17 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared memory layer for request.security() inter-process communication.
|
|
3
|
+
|
|
4
|
+
Two types of shared memory blocks:
|
|
5
|
+
|
|
6
|
+
1. **SyncBlock** — single fixed-size block containing metadata for all security slots
|
|
7
|
+
(timestamp, version, result_size, flags per slot).
|
|
8
|
+
|
|
9
|
+
2. **ResultBlock** — one per security ID, holds the pickled result value.
|
|
10
|
+
Reallocated with doubled size when the pickle data outgrows the current block.
|
|
11
|
+
"""
|
|
12
|
+
import pickle
|
|
13
|
+
import struct
|
|
14
|
+
from multiprocessing.shared_memory import SharedMemory
|
|
15
|
+
|
|
16
|
+
# Per-slot layout in the sync block:
|
|
17
|
+
# offset 0: int64 last_timestamp (8 bytes) — last bar timestamp from security process
|
|
18
|
+
# offset 8: uint32 version (4 bytes) — result block version (incremented on realloc)
|
|
19
|
+
# offset 12: uint32 result_size (4 bytes) — current pickle data size in bytes
|
|
20
|
+
# offset 16: int64 target_time (8 bytes) — target time the process should advance to
|
|
21
|
+
# offset 24: uint8 flags (1 byte) — state flags
|
|
22
|
+
# offset 25: 7 bytes pad (alignment to 32)
|
|
23
|
+
# offset 32: float64 dev_open (8 bytes) — developing HTF bar OHLCV (lookahead_on live)
|
|
24
|
+
# offset 40: float64 dev_high (8 bytes)
|
|
25
|
+
# offset 48: float64 dev_low (8 bytes)
|
|
26
|
+
# offset 56: float64 dev_close (8 bytes)
|
|
27
|
+
# offset 64: float64 dev_volume (8 bytes)
|
|
28
|
+
# offset 72: int64 dev_time (8 bytes) — developing HTF bar timestamp (ms);
|
|
29
|
+
# reused as period_end_exclusive (ms) by the live LTF-window
|
|
30
|
+
# path, which carries no pushed OHLCV (see FLAG_LTF_WINDOW)
|
|
31
|
+
# offset 80: uint32 write_seq (4 bytes) — bumped on every result write
|
|
32
|
+
# (value or na); cache key for the security-child
|
|
33
|
+
# cross-context read cache (version alone only tracks
|
|
34
|
+
# reallocation, not writes)
|
|
35
|
+
# offset 84: 4 bytes pad (alignment to 88)
|
|
36
|
+
# offset 88: int64 ltf_period_start (8 bytes) — file-backed LTF rounds: the
|
|
37
|
+
# chart bar's period START (ms). The child runs every
|
|
38
|
+
# feed bar up to target_time for expression state, but
|
|
39
|
+
# only values written at/after this instant belong in
|
|
40
|
+
# the flushed intrabar array (TradingView arrays hold
|
|
41
|
+
# only the bar's OWN period).
|
|
42
|
+
#
|
|
43
|
+
# Total per slot: 96 bytes
|
|
44
|
+
SLOT_FORMAT = '<qIIqB'
|
|
45
|
+
SLOT_SIZE = 96
|
|
46
|
+
SLOT_DATA_SIZE = struct.calcsize(SLOT_FORMAT) # 25 bytes — original fields only
|
|
47
|
+
_WRITE_SEQ_OFFSET = 80
|
|
48
|
+
_LTF_PERIOD_START_OFFSET = 88
|
|
49
|
+
|
|
50
|
+
# Offset of the developing-bar block within a slot.
|
|
51
|
+
_DEV_OHLCV_OFFSET = 32
|
|
52
|
+
_DEV_OHLCV_FORMAT = '<dddddq' # open, high, low, close, volume, time(ms)
|
|
53
|
+
|
|
54
|
+
# Flag bits
|
|
55
|
+
FLAG_HAS_DATA = 0x01 # result block contains valid data
|
|
56
|
+
FLAG_SAME_CONTEXT = 0x02 # same symbol + TF as chart (no process needed)
|
|
57
|
+
FLAG_IS_DEVELOPING = 0x04 # current target_time refers to a developing (open) HTF bar
|
|
58
|
+
FLAG_CLOSED_OVERRIDE = 0x08 # closed-bar OHLCV is supplied via SyncBlock (live mode);
|
|
59
|
+
# subprocess must use SyncBlock OHLCV, not the .ohlcv file
|
|
60
|
+
FLAG_LTF_WINDOW = 0x10 # live request.security_lower_tf window round: subprocess pulls
|
|
61
|
+
# intrabars from its own LTF streamer (not pushed OHLCV);
|
|
62
|
+
# target_time = period_start, dev_time = period_end_exclusive
|
|
63
|
+
FLAG_LTF_CHART_DEVELOPING = 0x20 # within an LTF-window round, the chart bar is still
|
|
64
|
+
# developing (keep the developing tail); clear means
|
|
65
|
+
# the chart bar has closed (finalize, publish full period)
|
|
66
|
+
FLAG_LTF_LIVE_PHASE = 0x40 # within an LTF-window round, the chart has crossed the
|
|
67
|
+
# warmup->live transition (barstate realtime, not history)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_ltf_window(flags: int) -> bool:
|
|
71
|
+
"""Whether a slot is in a live LTF-window round (see :data:`FLAG_LTF_WINDOW`)."""
|
|
72
|
+
return bool(flags & FLAG_LTF_WINDOW)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def is_ltf_chart_developing(flags: int) -> bool:
|
|
76
|
+
"""Whether the chart bar of an LTF-window round is still developing."""
|
|
77
|
+
return bool(flags & FLAG_LTF_CHART_DEVELOPING)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_ltf_live_phase(flags: int) -> bool:
|
|
81
|
+
"""Whether an LTF-window round is past the warmup->live transition."""
|
|
82
|
+
return bool(flags & FLAG_LTF_LIVE_PHASE)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Initial result block size
|
|
86
|
+
INITIAL_RESULT_SIZE = 4096
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _result_block_name(sec_id: str, version: int) -> str:
|
|
90
|
+
"""Generate SharedMemory name for a result block."""
|
|
91
|
+
safe_id = sec_id.replace('\xb7', '_')
|
|
92
|
+
return f"pyne_{safe_id}_v{version}"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class SyncBlock:
|
|
96
|
+
"""
|
|
97
|
+
Fixed-size shared memory block containing sync metadata for all security slots.
|
|
98
|
+
|
|
99
|
+
Layout: N consecutive slots of SLOT_SIZE bytes each.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, sec_ids: list[str], *, create: bool = True, name: str | None = None):
|
|
103
|
+
self._sec_ids = list(sec_ids)
|
|
104
|
+
self._index = {sid: i for i, sid in enumerate(sec_ids)}
|
|
105
|
+
total_size = max(SLOT_SIZE * len(sec_ids), 1)
|
|
106
|
+
|
|
107
|
+
if create:
|
|
108
|
+
self._shm = SharedMemory(
|
|
109
|
+
name=name, create=True, size=total_size
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
self._shm = SharedMemory(name=name, create=False)
|
|
113
|
+
|
|
114
|
+
buf = self._shm.buf
|
|
115
|
+
assert buf is not None
|
|
116
|
+
self._buf: memoryview = buf
|
|
117
|
+
|
|
118
|
+
if create:
|
|
119
|
+
self._buf[:total_size] = b'\x00' * total_size
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def name(self) -> str:
|
|
123
|
+
return self._shm.name
|
|
124
|
+
|
|
125
|
+
def _offset(self, sec_id: str) -> int:
|
|
126
|
+
return self._index[sec_id] * SLOT_SIZE
|
|
127
|
+
|
|
128
|
+
def get_slot(self, sec_id: str) -> tuple[int, int, int, int, int]:
|
|
129
|
+
"""
|
|
130
|
+
Read a slot's fields.
|
|
131
|
+
|
|
132
|
+
:return: (last_timestamp, version, result_size, target_time, flags)
|
|
133
|
+
"""
|
|
134
|
+
off = self._offset(sec_id)
|
|
135
|
+
return struct.unpack_from(SLOT_FORMAT, self._buf, off)
|
|
136
|
+
|
|
137
|
+
def set_target_time(self, sec_id: str, target_time: int):
|
|
138
|
+
"""Set the target_time field for a slot."""
|
|
139
|
+
off = self._offset(sec_id)
|
|
140
|
+
struct.pack_into('<q', self._buf, off + 16, target_time)
|
|
141
|
+
|
|
142
|
+
def get_target_time(self, sec_id: str) -> int:
|
|
143
|
+
"""Read the target_time field for a slot."""
|
|
144
|
+
off = self._offset(sec_id)
|
|
145
|
+
return struct.unpack_from('<q', self._buf, off + 16)[0]
|
|
146
|
+
|
|
147
|
+
def set_ltf_period_end(self, sec_id: str, period_end_exclusive: int):
|
|
148
|
+
"""Set the LTF chart-period end (ms, exclusive) for a slot.
|
|
149
|
+
|
|
150
|
+
Reuses the ``dev_time`` field (offset 72): the live LTF-window path
|
|
151
|
+
pushes no developing OHLCV, so this slot is free to carry the chart
|
|
152
|
+
bar's period end alongside ``target_time`` (the period start).
|
|
153
|
+
"""
|
|
154
|
+
off = self._offset(sec_id)
|
|
155
|
+
struct.pack_into('<q', self._buf, off + 72, period_end_exclusive)
|
|
156
|
+
|
|
157
|
+
def get_ltf_period_end(self, sec_id: str) -> int:
|
|
158
|
+
"""Read the LTF chart-period end (ms, exclusive) for a slot."""
|
|
159
|
+
off = self._offset(sec_id)
|
|
160
|
+
return struct.unpack_from('<q', self._buf, off + 72)[0]
|
|
161
|
+
|
|
162
|
+
def set_timestamp(self, sec_id: str, timestamp: int):
|
|
163
|
+
"""Set the last_timestamp field."""
|
|
164
|
+
off = self._offset(sec_id)
|
|
165
|
+
struct.pack_into('<q', self._buf, off, timestamp)
|
|
166
|
+
|
|
167
|
+
def get_timestamp(self, sec_id: str) -> int:
|
|
168
|
+
"""Read the last_timestamp field."""
|
|
169
|
+
off = self._offset(sec_id)
|
|
170
|
+
return struct.unpack_from('<q', self._buf, off)[0]
|
|
171
|
+
|
|
172
|
+
def set_result_meta(self, sec_id: str, version: int, result_size: int):
|
|
173
|
+
"""Set version and result_size fields."""
|
|
174
|
+
off = self._offset(sec_id)
|
|
175
|
+
struct.pack_into('<II', self._buf, off + 8, version, result_size)
|
|
176
|
+
|
|
177
|
+
def get_result_meta(self, sec_id: str) -> tuple[int, int]:
|
|
178
|
+
"""
|
|
179
|
+
Read version and result_size.
|
|
180
|
+
|
|
181
|
+
:return: (version, result_size)
|
|
182
|
+
"""
|
|
183
|
+
off = self._offset(sec_id)
|
|
184
|
+
return struct.unpack_from('<II', self._buf, off + 8)
|
|
185
|
+
|
|
186
|
+
def set_ltf_period_start(self, sec_id: str, period_start_ms: int) -> None:
|
|
187
|
+
"""Set the file-backed LTF round's chart-bar period start (ms)."""
|
|
188
|
+
off = self._offset(sec_id) + _LTF_PERIOD_START_OFFSET
|
|
189
|
+
struct.pack_into('<q', self._buf, off, period_start_ms)
|
|
190
|
+
|
|
191
|
+
def get_ltf_period_start(self, sec_id: str) -> int:
|
|
192
|
+
"""Read the file-backed LTF round's chart-bar period start (ms)."""
|
|
193
|
+
off = self._offset(sec_id) + _LTF_PERIOD_START_OFFSET
|
|
194
|
+
return struct.unpack_from('<q', self._buf, off)[0]
|
|
195
|
+
|
|
196
|
+
def bump_write_seq(self, sec_id: str) -> None:
|
|
197
|
+
"""Increment the slot's write sequence (wraps at 2^32)."""
|
|
198
|
+
off = self._offset(sec_id) + _WRITE_SEQ_OFFSET
|
|
199
|
+
seq = struct.unpack_from('<I', self._buf, off)[0]
|
|
200
|
+
struct.pack_into('<I', self._buf, off, (seq + 1) & 0xFFFFFFFF)
|
|
201
|
+
|
|
202
|
+
def get_write_seq(self, sec_id: str) -> int:
|
|
203
|
+
"""Read the slot's write sequence."""
|
|
204
|
+
off = self._offset(sec_id) + _WRITE_SEQ_OFFSET
|
|
205
|
+
return struct.unpack_from('<I', self._buf, off)[0]
|
|
206
|
+
|
|
207
|
+
def set_flags(self, sec_id: str, flags: int):
|
|
208
|
+
"""Set the flags byte."""
|
|
209
|
+
off = self._offset(sec_id)
|
|
210
|
+
struct.pack_into('<B', self._buf, off + 24, flags)
|
|
211
|
+
|
|
212
|
+
def get_flags(self, sec_id: str) -> int:
|
|
213
|
+
"""Read the flags byte."""
|
|
214
|
+
off = self._offset(sec_id)
|
|
215
|
+
return struct.unpack_from('<B', self._buf, off + 24)[0]
|
|
216
|
+
|
|
217
|
+
def set_developing_bar(
|
|
218
|
+
self, sec_id: str,
|
|
219
|
+
dev_open: float, dev_high: float, dev_low: float,
|
|
220
|
+
dev_close: float, dev_volume: float, dev_time: int,
|
|
221
|
+
):
|
|
222
|
+
"""Write developing HTF bar OHLCV+time for live ``lookahead_on``."""
|
|
223
|
+
off = self._offset(sec_id) + _DEV_OHLCV_OFFSET
|
|
224
|
+
struct.pack_into(
|
|
225
|
+
_DEV_OHLCV_FORMAT, self._buf, off,
|
|
226
|
+
dev_open, dev_high, dev_low, dev_close, dev_volume, dev_time,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def get_developing_bar(
|
|
230
|
+
self, sec_id: str,
|
|
231
|
+
) -> tuple[float, float, float, float, float, int]:
|
|
232
|
+
"""Read developing HTF bar OHLCV+time.
|
|
233
|
+
|
|
234
|
+
:return: (open, high, low, close, volume, time_ms)
|
|
235
|
+
"""
|
|
236
|
+
off = self._offset(sec_id) + _DEV_OHLCV_OFFSET
|
|
237
|
+
return struct.unpack_from(_DEV_OHLCV_FORMAT, self._buf, off)
|
|
238
|
+
|
|
239
|
+
def close(self):
|
|
240
|
+
"""Close the shared memory (does not unlink)."""
|
|
241
|
+
self._shm.close()
|
|
242
|
+
|
|
243
|
+
def unlink(self):
|
|
244
|
+
"""Unlink (destroy) the shared memory."""
|
|
245
|
+
try:
|
|
246
|
+
self._shm.unlink()
|
|
247
|
+
except FileNotFoundError:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class ResultBlock:
|
|
252
|
+
"""
|
|
253
|
+
Per-security-ID shared memory block holding the pickled result value.
|
|
254
|
+
|
|
255
|
+
Supports automatic reallocation when data outgrows the block.
|
|
256
|
+
The version number is embedded in the SharedMemory name to allow
|
|
257
|
+
readers to detect and re-attach after reallocation.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
def __init__(self, sec_id: str, *, create: bool = True,
|
|
261
|
+
version: int = 0, size: int = INITIAL_RESULT_SIZE):
|
|
262
|
+
self._sec_id = sec_id
|
|
263
|
+
self._version = version
|
|
264
|
+
name = _result_block_name(sec_id, version)
|
|
265
|
+
|
|
266
|
+
if create:
|
|
267
|
+
try:
|
|
268
|
+
self._shm = SharedMemory(name=name, create=True, size=size)
|
|
269
|
+
except FileExistsError:
|
|
270
|
+
stale = SharedMemory(name=name, create=False)
|
|
271
|
+
stale.close()
|
|
272
|
+
stale.unlink()
|
|
273
|
+
self._shm = SharedMemory(name=name, create=True, size=size)
|
|
274
|
+
else:
|
|
275
|
+
self._shm = SharedMemory(name=name, create=False)
|
|
276
|
+
|
|
277
|
+
buf = self._shm.buf
|
|
278
|
+
assert buf is not None
|
|
279
|
+
self._buf: memoryview = buf
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def sec_id(self) -> str:
|
|
283
|
+
return self._sec_id
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def version(self) -> int:
|
|
287
|
+
return self._version
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def size(self) -> int:
|
|
291
|
+
return self._shm.size
|
|
292
|
+
|
|
293
|
+
def write(self, data: bytes, sync_block: SyncBlock) -> int:
|
|
294
|
+
"""
|
|
295
|
+
Write pickle data to the result block. Reallocates if needed.
|
|
296
|
+
|
|
297
|
+
:param data: Pickled bytes to write
|
|
298
|
+
:param sync_block: SyncBlock to update version/size metadata
|
|
299
|
+
:return: New version number
|
|
300
|
+
"""
|
|
301
|
+
if len(data) > self._shm.size:
|
|
302
|
+
new_size = max(len(data) * 2, INITIAL_RESULT_SIZE)
|
|
303
|
+
new_version = self._version + 1
|
|
304
|
+
new_name = _result_block_name(self._sec_id, new_version)
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
new_shm = SharedMemory(name=new_name, create=True, size=new_size)
|
|
308
|
+
except FileExistsError:
|
|
309
|
+
# A segment of this exact name leaked from an earlier run that
|
|
310
|
+
# was killed before cleanup (the name is deterministic per
|
|
311
|
+
# sec_id+version). Drop the stale block and recreate, mirroring
|
|
312
|
+
# the initial-allocation path above.
|
|
313
|
+
stale = SharedMemory(name=new_name, create=False)
|
|
314
|
+
stale.close()
|
|
315
|
+
stale.unlink()
|
|
316
|
+
new_shm = SharedMemory(name=new_name, create=True, size=new_size)
|
|
317
|
+
new_buf = new_shm.buf
|
|
318
|
+
assert new_buf is not None
|
|
319
|
+
new_buf[:len(data)] = data
|
|
320
|
+
|
|
321
|
+
old_shm = self._shm
|
|
322
|
+
self._shm = new_shm
|
|
323
|
+
self._buf = new_buf
|
|
324
|
+
self._version = new_version
|
|
325
|
+
|
|
326
|
+
old_shm.close()
|
|
327
|
+
try:
|
|
328
|
+
old_shm.unlink()
|
|
329
|
+
except FileNotFoundError:
|
|
330
|
+
pass
|
|
331
|
+
else:
|
|
332
|
+
self._buf[:len(data)] = data
|
|
333
|
+
|
|
334
|
+
sync_block.set_result_meta(self._sec_id, self._version, len(data))
|
|
335
|
+
sync_block.bump_write_seq(self._sec_id)
|
|
336
|
+
return self._version
|
|
337
|
+
|
|
338
|
+
def read(self, size: int) -> bytes:
|
|
339
|
+
"""
|
|
340
|
+
Read raw bytes from the result block.
|
|
341
|
+
|
|
342
|
+
:param size: Number of bytes to read
|
|
343
|
+
:return: Raw bytes
|
|
344
|
+
"""
|
|
345
|
+
return bytes(self._buf[:size])
|
|
346
|
+
|
|
347
|
+
def close(self):
|
|
348
|
+
"""Close the shared memory (does not unlink)."""
|
|
349
|
+
self._shm.close()
|
|
350
|
+
|
|
351
|
+
def unlink(self):
|
|
352
|
+
"""Unlink (destroy) the shared memory."""
|
|
353
|
+
try:
|
|
354
|
+
self._shm.unlink()
|
|
355
|
+
except FileNotFoundError:
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class ResultReader:
|
|
360
|
+
"""
|
|
361
|
+
Reader-side handle for a security result block.
|
|
362
|
+
|
|
363
|
+
Tracks the current version and re-attaches when the writer reallocates.
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
def __init__(self, sec_id: str):
|
|
367
|
+
self._sec_id = sec_id
|
|
368
|
+
self._shm: SharedMemory | None = None
|
|
369
|
+
self._version: int = -1
|
|
370
|
+
self._cached_seq: int = -1
|
|
371
|
+
self._cached_value = None
|
|
372
|
+
|
|
373
|
+
def read(self, sync_block: SyncBlock, default=None):
|
|
374
|
+
"""
|
|
375
|
+
Read the latest result value from shared memory.
|
|
376
|
+
|
|
377
|
+
:param sync_block: SyncBlock to check version/size
|
|
378
|
+
:param default: Value to return if no data available
|
|
379
|
+
:return: Unpickled result value, or default
|
|
380
|
+
"""
|
|
381
|
+
version, result_size = sync_block.get_result_meta(self._sec_id)
|
|
382
|
+
|
|
383
|
+
if result_size == 0:
|
|
384
|
+
return default
|
|
385
|
+
|
|
386
|
+
# Re-attach if version changed (writer reallocated)
|
|
387
|
+
if version != self._version:
|
|
388
|
+
if self._shm is not None:
|
|
389
|
+
self._shm.close()
|
|
390
|
+
name = _result_block_name(self._sec_id, version)
|
|
391
|
+
self._shm = SharedMemory(name=name, create=False)
|
|
392
|
+
self._version = version
|
|
393
|
+
|
|
394
|
+
shm = self._shm
|
|
395
|
+
assert shm is not None
|
|
396
|
+
buf = shm.buf
|
|
397
|
+
assert buf is not None
|
|
398
|
+
return pickle.loads(buf[:result_size])
|
|
399
|
+
|
|
400
|
+
def read_cached(self, sync_block: SyncBlock, default=None):
|
|
401
|
+
"""Like :meth:`read`, but unpickle only when the slot's write sequence
|
|
402
|
+
changed since the last call — repeated reads of an unchanged result
|
|
403
|
+
return the SAME cached object.
|
|
404
|
+
|
|
405
|
+
For the security-child cross-context path only: a child re-runs the
|
|
406
|
+
whole script per bar (per intrabar for LTF), so it re-reads every peer
|
|
407
|
+
context's latest value on each run. Without the cache a peer's large
|
|
408
|
+
LTF array (tens of thousands of elements after a deep first round) is
|
|
409
|
+
re-unpickled per intrabar — quadratic work that turns a 30-second run
|
|
410
|
+
into hours. The trade-off is aliasing: a script mutating the returned
|
|
411
|
+
container would poison subsequent reads, so the chart-side protocol
|
|
412
|
+
keeps the fresh-unpickle :meth:`read`.
|
|
413
|
+
"""
|
|
414
|
+
seq = sync_block.get_write_seq(self._sec_id)
|
|
415
|
+
if seq == self._cached_seq:
|
|
416
|
+
return self._cached_value if self._cached_value is not None else default
|
|
417
|
+
value = self.read(sync_block, default)
|
|
418
|
+
self._cached_seq = seq
|
|
419
|
+
self._cached_value = None if value is default else value
|
|
420
|
+
return value
|
|
421
|
+
|
|
422
|
+
def close(self):
|
|
423
|
+
"""Close the reader's shared memory handle."""
|
|
424
|
+
if self._shm is not None:
|
|
425
|
+
self._shm.close()
|
|
426
|
+
self._shm = None
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def write_result(result_block: ResultBlock, sync_block: SyncBlock, value) -> int:
|
|
430
|
+
"""
|
|
431
|
+
Pickle a value and write it to the result block.
|
|
432
|
+
|
|
433
|
+
:param result_block: The result block to write to
|
|
434
|
+
:param sync_block: The sync block to update metadata
|
|
435
|
+
:param value: The value to pickle and store
|
|
436
|
+
:return: New version number
|
|
437
|
+
"""
|
|
438
|
+
data = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
|
|
439
|
+
return result_block.write(data, sync_block)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def write_na(result_block: ResultBlock, sync_block: SyncBlock) -> int:
|
|
443
|
+
"""
|
|
444
|
+
Write an empty result (na) — sets result_size to 0.
|
|
445
|
+
|
|
446
|
+
:param result_block: The result block (not modified)
|
|
447
|
+
:param sync_block: The sync block to update metadata
|
|
448
|
+
:return: Current version number
|
|
449
|
+
"""
|
|
450
|
+
sync_block.set_result_meta(
|
|
451
|
+
result_block.sec_id,
|
|
452
|
+
result_block.version,
|
|
453
|
+
0 # zero size = no data = na
|
|
454
|
+
)
|
|
455
|
+
sync_block.bump_write_seq(result_block.sec_id)
|
|
456
|
+
return result_block.version
|