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,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Currency rate provider for request.currency_rate().
|
|
3
|
+
|
|
4
|
+
Sources of exchange-rate data, in priority order:
|
|
5
|
+
|
|
6
|
+
* the chart symbol itself — when the chart is a currency pair (e.g. EURUSD
|
|
7
|
+
chart), ``lib.close`` is the up-to-the-second rate.
|
|
8
|
+
* a security context already attached to the run — backtest or live —
|
|
9
|
+
whose ``(basecurrency, currency)`` syminfo matches the requested pair.
|
|
10
|
+
The provider reads the security's latest ``close`` from its
|
|
11
|
+
:class:`ResultBlock` (the same channel ``__sec_read__`` uses).
|
|
12
|
+
* legacy file path: a sibling ``.toml`` describing a static
|
|
13
|
+
``.ohlcv`` file. Only relevant when older callers still pass plain
|
|
14
|
+
paths; live runs never carry these.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import struct
|
|
19
|
+
import bisect
|
|
20
|
+
from math import isnan
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
from .ohlcv_file import RECORD_SIZE
|
|
25
|
+
from .security_shm import ResultReader
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from .syminfo import SymInfo
|
|
29
|
+
from .security_shm import SyncBlock
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CurrencyRateProvider:
|
|
33
|
+
"""
|
|
34
|
+
Provides currency exchange rates from running security contexts.
|
|
35
|
+
|
|
36
|
+
Walks two input sources at construction time:
|
|
37
|
+
* ``sec_syminfos`` — per-sec_id :class:`SymInfo` for active security
|
|
38
|
+
contexts (the chart-side prefetch in live mode, the backtest
|
|
39
|
+
``.toml`` cache otherwise).
|
|
40
|
+
* ``security_data`` — legacy file paths kept for backward compatibility
|
|
41
|
+
with callers that have not been migrated yet.
|
|
42
|
+
|
|
43
|
+
The chart's own OHLCV is also recognized — when the chart symbol is
|
|
44
|
+
itself a currency pair (e.g. EURUSD), the provider returns
|
|
45
|
+
``lib.close`` directly instead of consulting any security context.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
__slots__ = (
|
|
49
|
+
'_pair_map', '_chart_pair',
|
|
50
|
+
'_sync_block', '_result_readers',
|
|
51
|
+
'_file_rate_cache',
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
security_data: 'dict[str, str | Path] | None' = None,
|
|
57
|
+
chart_syminfo: 'SymInfo | None' = None,
|
|
58
|
+
sec_syminfos: 'dict[str, SymInfo] | None' = None,
|
|
59
|
+
sync_block: 'SyncBlock | None' = None,
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
:param security_data: Legacy mapping of user keys to OHLCV file paths.
|
|
63
|
+
Used only when no matching ``sec_syminfo`` is
|
|
64
|
+
available (backtest fallback for callers that
|
|
65
|
+
have not provided pre-loaded syminfos).
|
|
66
|
+
:param chart_syminfo: Chart's SymInfo — if it carries basecurrency,
|
|
67
|
+
the chart itself becomes a rate source.
|
|
68
|
+
:param sec_syminfos: Per-sec_id :class:`SymInfo` for security
|
|
69
|
+
contexts active in this run. The provider
|
|
70
|
+
builds its ``(basecurrency, currency) →
|
|
71
|
+
sec_id`` lookup primarily from these.
|
|
72
|
+
:param sync_block: The shared :class:`SyncBlock` used by the
|
|
73
|
+
security infrastructure. Required when any rate
|
|
74
|
+
source is a sec_id (so :class:`ResultReader`
|
|
75
|
+
can fetch the latest pickled close from the
|
|
76
|
+
security's :class:`ResultBlock`).
|
|
77
|
+
"""
|
|
78
|
+
# Tagged source: ``('sec', sec_id)`` for in-process security
|
|
79
|
+
# contexts, ``('file', path)`` for legacy file-backed sources.
|
|
80
|
+
self._pair_map: dict[tuple[str, str], tuple[str, str]] = {}
|
|
81
|
+
self._chart_pair: tuple[str, str] | None = None
|
|
82
|
+
self._sync_block: 'SyncBlock | None' = sync_block
|
|
83
|
+
self._result_readers: dict[str, ResultReader] = {}
|
|
84
|
+
self._file_rate_cache: dict[str, tuple[list[int], list[float]]] = {}
|
|
85
|
+
|
|
86
|
+
self._build_pair_map(
|
|
87
|
+
security_data or {},
|
|
88
|
+
chart_syminfo,
|
|
89
|
+
sec_syminfos or {},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _build_pair_map(
|
|
93
|
+
self,
|
|
94
|
+
security_data: 'dict[str, str | Path]',
|
|
95
|
+
chart_syminfo: 'SymInfo | None',
|
|
96
|
+
sec_syminfos: 'dict[str, SymInfo]',
|
|
97
|
+
) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Index every available rate source by ``(basecurrency, currency)``.
|
|
100
|
+
|
|
101
|
+
Priority: chart pair > running security context > static file.
|
|
102
|
+
On collisions within a priority class the first entry wins; this
|
|
103
|
+
keeps the mapping deterministic across runs.
|
|
104
|
+
"""
|
|
105
|
+
if chart_syminfo and chart_syminfo.basecurrency:
|
|
106
|
+
self._chart_pair = (chart_syminfo.basecurrency, chart_syminfo.currency)
|
|
107
|
+
|
|
108
|
+
for sec_id, syminfo in sec_syminfos.items():
|
|
109
|
+
if not syminfo or not syminfo.basecurrency:
|
|
110
|
+
continue
|
|
111
|
+
pair = (syminfo.basecurrency, syminfo.currency)
|
|
112
|
+
self._pair_map.setdefault(pair, ('sec', sec_id))
|
|
113
|
+
|
|
114
|
+
from .syminfo import SymInfo
|
|
115
|
+
for _key, path in security_data.items():
|
|
116
|
+
ohlcv_path = self._resolve_ohlcv_path(path)
|
|
117
|
+
if ohlcv_path is None:
|
|
118
|
+
continue
|
|
119
|
+
toml_path = Path(ohlcv_path).with_suffix('.toml')
|
|
120
|
+
if not toml_path.exists():
|
|
121
|
+
continue
|
|
122
|
+
try:
|
|
123
|
+
syminfo = SymInfo.load_toml(toml_path)
|
|
124
|
+
except (ValueError, KeyError):
|
|
125
|
+
continue
|
|
126
|
+
if not syminfo.basecurrency:
|
|
127
|
+
continue
|
|
128
|
+
pair = (syminfo.basecurrency, syminfo.currency)
|
|
129
|
+
existing = self._pair_map.get(pair)
|
|
130
|
+
if existing is not None:
|
|
131
|
+
# Sec-backed entries (live or in-process backtest contexts)
|
|
132
|
+
# outrank static files; among files, the larger one wins to
|
|
133
|
+
# match historical behaviour.
|
|
134
|
+
if existing[0] == 'sec':
|
|
135
|
+
continue
|
|
136
|
+
if self._get_ohlcv_bar_count(ohlcv_path) <= \
|
|
137
|
+
self._get_ohlcv_bar_count(existing[1]):
|
|
138
|
+
continue
|
|
139
|
+
self._pair_map[pair] = ('file', ohlcv_path)
|
|
140
|
+
|
|
141
|
+
def get_rate(self, from_cur: str, to_cur: str, timestamp: int) -> float:
|
|
142
|
+
"""
|
|
143
|
+
Get exchange rate for a currency pair at a given timestamp.
|
|
144
|
+
|
|
145
|
+
:param from_cur: Source currency code (e.g. "EUR")
|
|
146
|
+
:param to_cur: Target currency code (e.g. "USD")
|
|
147
|
+
:param timestamp: UNIX timestamp (seconds)
|
|
148
|
+
:return: Exchange rate, or NaN if unavailable
|
|
149
|
+
"""
|
|
150
|
+
if from_cur == "NONE" or to_cur == "NONE":
|
|
151
|
+
return float('nan')
|
|
152
|
+
if from_cur == to_cur:
|
|
153
|
+
return 1.0
|
|
154
|
+
|
|
155
|
+
if self._chart_pair == (from_cur, to_cur):
|
|
156
|
+
from .. import lib
|
|
157
|
+
return float(lib.close)
|
|
158
|
+
if self._chart_pair == (to_cur, from_cur):
|
|
159
|
+
from .. import lib
|
|
160
|
+
close = float(lib.close)
|
|
161
|
+
return 1.0 / close if close and not isnan(close) and close != 0.0 else float('nan')
|
|
162
|
+
|
|
163
|
+
if (from_cur, to_cur) in self._pair_map:
|
|
164
|
+
return self._lookup(self._pair_map[(from_cur, to_cur)], timestamp)
|
|
165
|
+
|
|
166
|
+
if (to_cur, from_cur) in self._pair_map:
|
|
167
|
+
rate = self._lookup(self._pair_map[(to_cur, from_cur)], timestamp)
|
|
168
|
+
return 1.0 / rate if rate and not isnan(rate) and rate != 0.0 else float('nan')
|
|
169
|
+
|
|
170
|
+
return float('nan')
|
|
171
|
+
|
|
172
|
+
def _lookup(self, source: tuple[str, str], timestamp: int) -> float:
|
|
173
|
+
"""Dispatch on the tagged source type."""
|
|
174
|
+
kind, value = source
|
|
175
|
+
if kind == 'sec':
|
|
176
|
+
return self._lookup_sec(value)
|
|
177
|
+
return self._lookup_file(value, timestamp)
|
|
178
|
+
|
|
179
|
+
def _lookup_sec(self, sec_id: str) -> float:
|
|
180
|
+
"""Read the latest pickled close from the security's result block."""
|
|
181
|
+
if self._sync_block is None:
|
|
182
|
+
return float('nan')
|
|
183
|
+
reader = self._result_readers.get(sec_id)
|
|
184
|
+
if reader is None:
|
|
185
|
+
reader = ResultReader(sec_id)
|
|
186
|
+
self._result_readers[sec_id] = reader
|
|
187
|
+
try:
|
|
188
|
+
value = reader.read(self._sync_block, default=None)
|
|
189
|
+
except (FileNotFoundError, OSError):
|
|
190
|
+
# Result block not attached yet — subprocess hasn't written
|
|
191
|
+
# its first bar. Caller treats NaN as "not ready" and emits
|
|
192
|
+
# NA for this bar.
|
|
193
|
+
return float('nan')
|
|
194
|
+
if value is None:
|
|
195
|
+
return float('nan')
|
|
196
|
+
if isinstance(value, (int, float)):
|
|
197
|
+
return float(value)
|
|
198
|
+
if isinstance(value, tuple) and value and isinstance(value[0], (int, float)):
|
|
199
|
+
return float(value[0])
|
|
200
|
+
return float('nan')
|
|
201
|
+
|
|
202
|
+
def _lookup_file(self, ohlcv_path: str, timestamp: int) -> float:
|
|
203
|
+
"""Static-file path (backtest only)."""
|
|
204
|
+
if ohlcv_path not in self._file_rate_cache:
|
|
205
|
+
self._load_ohlcv(ohlcv_path)
|
|
206
|
+
timestamps, closes = self._file_rate_cache[ohlcv_path]
|
|
207
|
+
if not timestamps:
|
|
208
|
+
return float('nan')
|
|
209
|
+
idx = bisect.bisect_right(timestamps, timestamp) - 1
|
|
210
|
+
if idx < 0:
|
|
211
|
+
return float('nan')
|
|
212
|
+
return closes[idx]
|
|
213
|
+
|
|
214
|
+
def _load_ohlcv(self, ohlcv_path: str) -> None:
|
|
215
|
+
"""Load timestamps and close prices from binary OHLCV file."""
|
|
216
|
+
timestamps: list[int] = []
|
|
217
|
+
closes: list[float] = []
|
|
218
|
+
path = Path(ohlcv_path)
|
|
219
|
+
if not path.exists():
|
|
220
|
+
self._file_rate_cache[ohlcv_path] = (timestamps, closes)
|
|
221
|
+
return
|
|
222
|
+
file_size = path.stat().st_size
|
|
223
|
+
bar_count = file_size // RECORD_SIZE
|
|
224
|
+
with open(path, 'rb') as f:
|
|
225
|
+
data = f.read()
|
|
226
|
+
for i in range(bar_count):
|
|
227
|
+
offset = i * RECORD_SIZE
|
|
228
|
+
ts = struct.unpack_from('I', data, offset)[0]
|
|
229
|
+
close = struct.unpack_from('f', data, offset + 16)[0]
|
|
230
|
+
timestamps.append(ts)
|
|
231
|
+
closes.append(close)
|
|
232
|
+
self._file_rate_cache[ohlcv_path] = (timestamps, closes)
|
|
233
|
+
|
|
234
|
+
def close(self) -> None:
|
|
235
|
+
"""Close every attached :class:`ResultReader`."""
|
|
236
|
+
for r in self._result_readers.values():
|
|
237
|
+
r.close()
|
|
238
|
+
self._result_readers.clear()
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def _get_ohlcv_bar_count(path: str) -> int:
|
|
242
|
+
"""Get the number of bars in an OHLCV file without opening it."""
|
|
243
|
+
try:
|
|
244
|
+
return Path(path).stat().st_size // RECORD_SIZE
|
|
245
|
+
except OSError:
|
|
246
|
+
return 0
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def _resolve_ohlcv_path(path: str | Path) -> str | None:
|
|
250
|
+
"""Resolve OHLCV file path, adding the ``.ohlcv`` extension if needed.
|
|
251
|
+
|
|
252
|
+
A dot inside the name belongs to the symbol (e.g. a perpetual
|
|
253
|
+
``BTCUSDT.P``), so append by name rather than ``with_suffix`` which
|
|
254
|
+
would clobber the symbol's own dotted tail.
|
|
255
|
+
"""
|
|
256
|
+
p = Path(path)
|
|
257
|
+
if p.name.endswith('.ohlcv'):
|
|
258
|
+
return str(p) if p.exists() else None
|
|
259
|
+
ohlcv_p = p.with_name(p.name + '.ohlcv')
|
|
260
|
+
if ohlcv_p.exists():
|
|
261
|
+
return str(ohlcv_p)
|
|
262
|
+
return str(p) if p.exists() else None
|