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
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())
|