desic-okx-agent 0.2.1 → 0.3.0

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 (236) hide show
  1. package/README.en.md +90 -10
  2. package/README.md +76 -10
  3. package/dist/account/private-websocket.js +4 -4
  4. package/dist/account/private-websocket.js.map +1 -1
  5. package/dist/account/service.d.ts +12 -1
  6. package/dist/account/service.js +18 -0
  7. package/dist/account/service.js.map +1 -1
  8. package/dist/bars/rate-limiter.d.ts +18 -0
  9. package/dist/bars/rate-limiter.js +84 -0
  10. package/dist/bars/rate-limiter.js.map +1 -0
  11. package/dist/bars/schema.d.ts +36 -0
  12. package/dist/bars/schema.js +134 -0
  13. package/dist/bars/schema.js.map +1 -0
  14. package/dist/bars/service.d.ts +60 -0
  15. package/dist/bars/service.js +120 -0
  16. package/dist/bars/service.js.map +1 -0
  17. package/dist/bars/store.d.ts +105 -0
  18. package/dist/bars/store.js +415 -0
  19. package/dist/bars/store.js.map +1 -0
  20. package/dist/bars/timeframe.d.ts +40 -0
  21. package/dist/bars/timeframe.js +146 -0
  22. package/dist/bars/timeframe.js.map +1 -0
  23. package/dist/bars/types.d.ts +68 -0
  24. package/dist/bars/types.js +13 -0
  25. package/dist/bars/types.js.map +1 -0
  26. package/dist/cli/data-render.d.ts +37 -0
  27. package/dist/cli/data-render.js +143 -0
  28. package/dist/cli/data-render.js.map +1 -0
  29. package/dist/cli/doctor.js +7 -3
  30. package/dist/cli/doctor.js.map +1 -1
  31. package/dist/cli/index.js +757 -26
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/live-render.d.ts +24 -0
  34. package/dist/cli/live-render.js +85 -0
  35. package/dist/cli/live-render.js.map +1 -0
  36. package/dist/cli/range.d.ts +28 -0
  37. package/dist/cli/range.js +63 -0
  38. package/dist/cli/range.js.map +1 -0
  39. package/dist/cli/render.js +3 -0
  40. package/dist/cli/render.js.map +1 -1
  41. package/dist/cli/strategy-render.d.ts +36 -0
  42. package/dist/cli/strategy-render.js +391 -0
  43. package/dist/cli/strategy-render.js.map +1 -0
  44. package/dist/cli/width.d.ts +18 -0
  45. package/dist/cli/width.js +71 -0
  46. package/dist/cli/width.js.map +1 -0
  47. package/dist/config/loader.js +1 -1
  48. package/dist/config/schema.d.ts +7 -0
  49. package/dist/config/schema.js +24 -0
  50. package/dist/config/schema.js.map +1 -1
  51. package/dist/core/okx-client.d.ts +9 -1
  52. package/dist/core/okx-client.js +14 -5
  53. package/dist/core/okx-client.js.map +1 -1
  54. package/dist/i18n/locale.d.ts +24 -0
  55. package/dist/i18n/locale.js +65 -0
  56. package/dist/i18n/locale.js.map +1 -0
  57. package/dist/i18n/messages.d.ts +333 -0
  58. package/dist/i18n/messages.js +660 -0
  59. package/dist/i18n/messages.js.map +1 -0
  60. package/dist/live/account-snapshot.d.ts +30 -0
  61. package/dist/live/account-snapshot.js +130 -0
  62. package/dist/live/account-snapshot.js.map +1 -0
  63. package/dist/live/cutoff-queue.d.ts +42 -0
  64. package/dist/live/cutoff-queue.js +69 -0
  65. package/dist/live/cutoff-queue.js.map +1 -0
  66. package/dist/live/execution-key.d.ts +23 -0
  67. package/dist/live/execution-key.js +31 -0
  68. package/dist/live/execution-key.js.map +1 -0
  69. package/dist/live/failures.d.ts +37 -0
  70. package/dist/live/failures.js +57 -0
  71. package/dist/live/failures.js.map +1 -0
  72. package/dist/live/gates.d.ts +65 -0
  73. package/dist/live/gates.js +136 -0
  74. package/dist/live/gates.js.map +1 -0
  75. package/dist/live/loop.d.ts +56 -0
  76. package/dist/live/loop.js +197 -0
  77. package/dist/live/loop.js.map +1 -0
  78. package/dist/live/preconditions.d.ts +48 -0
  79. package/dist/live/preconditions.js +69 -0
  80. package/dist/live/preconditions.js.map +1 -0
  81. package/dist/live/reconcile.d.ts +46 -0
  82. package/dist/live/reconcile.js +104 -0
  83. package/dist/live/reconcile.js.map +1 -0
  84. package/dist/live/runner.d.ts +57 -0
  85. package/dist/live/runner.js +160 -0
  86. package/dist/live/runner.js.map +1 -0
  87. package/dist/live/schema.d.ts +18 -0
  88. package/dist/live/schema.js +91 -0
  89. package/dist/live/schema.js.map +1 -0
  90. package/dist/live/service.d.ts +144 -0
  91. package/dist/live/service.js +303 -0
  92. package/dist/live/service.js.map +1 -0
  93. package/dist/live/session.d.ts +85 -0
  94. package/dist/live/session.js +234 -0
  95. package/dist/live/session.js.map +1 -0
  96. package/dist/live/sizing.d.ts +62 -0
  97. package/dist/live/sizing.js +79 -0
  98. package/dist/live/sizing.js.map +1 -0
  99. package/dist/live/store.d.ts +123 -0
  100. package/dist/live/store.js +350 -0
  101. package/dist/live/store.js.map +1 -0
  102. package/dist/live/types.d.ts +82 -0
  103. package/dist/live/types.js +2 -0
  104. package/dist/live/types.js.map +1 -0
  105. package/dist/market/websocket.d.ts +16 -1
  106. package/dist/market/websocket.js +60 -5
  107. package/dist/market/websocket.js.map +1 -1
  108. package/dist/mcp/server.d.ts +1 -0
  109. package/dist/mcp/server.js +15 -1
  110. package/dist/mcp/server.js.map +1 -1
  111. package/dist/network/connectivity.d.ts +9 -1
  112. package/dist/network/connectivity.js +28 -1
  113. package/dist/network/connectivity.js.map +1 -1
  114. package/dist/report/chart-script.d.ts +12 -0
  115. package/dist/report/chart-script.js +146 -0
  116. package/dist/report/chart-script.js.map +1 -0
  117. package/dist/report/compare-html.d.ts +8 -0
  118. package/dist/report/compare-html.js +254 -0
  119. package/dist/report/compare-html.js.map +1 -0
  120. package/dist/report/compare-script.d.ts +12 -0
  121. package/dist/report/compare-script.js +109 -0
  122. package/dist/report/compare-script.js.map +1 -0
  123. package/dist/report/compare.d.ts +61 -0
  124. package/dist/report/compare.js +205 -0
  125. package/dist/report/compare.js.map +1 -0
  126. package/dist/report/fetch.d.ts +20 -0
  127. package/dist/report/fetch.js +56 -0
  128. package/dist/report/fetch.js.map +1 -0
  129. package/dist/report/html.d.ts +54 -0
  130. package/dist/report/html.js +641 -0
  131. package/dist/report/html.js.map +1 -0
  132. package/dist/report/open.d.ts +42 -0
  133. package/dist/report/open.js +114 -0
  134. package/dist/report/open.js.map +1 -0
  135. package/dist/runtime/server.d.ts +16 -1
  136. package/dist/runtime/server.js +112 -9
  137. package/dist/runtime/server.js.map +1 -1
  138. package/dist/setup/installer.d.ts +1 -0
  139. package/dist/setup/installer.js +8 -0
  140. package/dist/setup/installer.js.map +1 -1
  141. package/dist/setup/wizard.js +19 -22
  142. package/dist/setup/wizard.js.map +1 -1
  143. package/dist/strategy/constants.d.ts +23 -0
  144. package/dist/strategy/constants.js +24 -0
  145. package/dist/strategy/constants.js.map +1 -0
  146. package/dist/strategy/environment.d.ts +52 -0
  147. package/dist/strategy/environment.js +187 -0
  148. package/dist/strategy/environment.js.map +1 -0
  149. package/dist/strategy/instrument.d.ts +29 -0
  150. package/dist/strategy/instrument.js +39 -0
  151. package/dist/strategy/instrument.js.map +1 -0
  152. package/dist/strategy/optimize.d.ts +73 -0
  153. package/dist/strategy/optimize.js +113 -0
  154. package/dist/strategy/optimize.js.map +1 -0
  155. package/dist/strategy/parameter-space.d.ts +59 -0
  156. package/dist/strategy/parameter-space.js +221 -0
  157. package/dist/strategy/parameter-space.js.map +1 -0
  158. package/dist/strategy/python-bridge.d.ts +24 -0
  159. package/dist/strategy/python-bridge.js +114 -0
  160. package/dist/strategy/python-bridge.js.map +1 -0
  161. package/dist/strategy/schema.d.ts +9 -0
  162. package/dist/strategy/schema.js +91 -0
  163. package/dist/strategy/schema.js.map +1 -0
  164. package/dist/strategy/service.d.ts +138 -0
  165. package/dist/strategy/service.js +745 -0
  166. package/dist/strategy/service.js.map +1 -0
  167. package/dist/strategy/settings.d.ts +162 -0
  168. package/dist/strategy/settings.js +243 -0
  169. package/dist/strategy/settings.js.map +1 -0
  170. package/dist/strategy/store.d.ts +96 -0
  171. package/dist/strategy/store.js +367 -0
  172. package/dist/strategy/store.js.map +1 -0
  173. package/dist/strategy/templates.d.ts +11 -0
  174. package/dist/strategy/templates.js +134 -0
  175. package/dist/strategy/templates.js.map +1 -0
  176. package/dist/strategy/types.d.ts +111 -0
  177. package/dist/strategy/types.js +2 -0
  178. package/dist/strategy/types.js.map +1 -0
  179. package/dist/tools/catalog.d.ts +16 -0
  180. package/dist/tools/catalog.js +118 -17
  181. package/dist/tools/catalog.js.map +1 -1
  182. package/dist/trade/service.d.ts +12 -0
  183. package/dist/trade/service.js +24 -7
  184. package/dist/trade/service.js.map +1 -1
  185. package/dist/tui/app.d.ts +23 -0
  186. package/dist/tui/app.js +322 -0
  187. package/dist/tui/app.js.map +1 -0
  188. package/dist/tui/commands.d.ts +70 -0
  189. package/dist/tui/commands.js +313 -0
  190. package/dist/tui/commands.js.map +1 -0
  191. package/dist/tui/entries.d.ts +17 -0
  192. package/dist/tui/entries.js +24 -0
  193. package/dist/tui/entries.js.map +1 -0
  194. package/dist/tui/execute.d.ts +26 -0
  195. package/dist/tui/execute.js +664 -0
  196. package/dist/tui/execute.js.map +1 -0
  197. package/dist/tui/history.d.ts +17 -0
  198. package/dist/tui/history.js +48 -0
  199. package/dist/tui/history.js.map +1 -0
  200. package/dist/tui/index.d.ts +8 -0
  201. package/dist/tui/index.js +48 -0
  202. package/dist/tui/index.js.map +1 -0
  203. package/dist/tui/line-editor.d.ts +44 -0
  204. package/dist/tui/line-editor.js +98 -0
  205. package/dist/tui/line-editor.js.map +1 -0
  206. package/dist/tui/progress.d.ts +23 -0
  207. package/dist/tui/progress.js +46 -0
  208. package/dist/tui/progress.js.map +1 -0
  209. package/dist/tui/settings-editor.d.ts +18 -0
  210. package/dist/tui/settings-editor.js +115 -0
  211. package/dist/tui/settings-editor.js.map +1 -0
  212. package/docs/live-trading.md +455 -0
  213. package/docs/strategy-research.md +597 -0
  214. package/package.json +10 -1
  215. package/python/desic_strategy/__init__.py +34 -0
  216. package/python/desic_strategy/actions.py +158 -0
  217. package/python/desic_strategy/context.py +164 -0
  218. package/python/desic_strategy/engine.py +614 -0
  219. package/python/desic_strategy/indicators.py +159 -0
  220. package/python/desic_strategy/live.py +253 -0
  221. package/python/desic_strategy/policy.py +193 -0
  222. package/python/desic_strategy/portfolio.py +152 -0
  223. package/python/desic_strategy/report.py +319 -0
  224. package/python/desic_strategy/runner.py +574 -0
  225. package/python/desic_strategy/timeframe.py +150 -0
  226. package/python/main.py +18 -0
  227. package/skills/okx-live-trading/SKILL.md +117 -0
  228. package/skills/okx-live-trading/agents/openai.yaml +9 -0
  229. package/skills/okx-live-trading/references/lifecycle.md +128 -0
  230. package/skills/okx-strategy-research/SKILL.md +113 -0
  231. package/skills/okx-strategy-research/agents/openai.yaml +9 -0
  232. package/skills/okx-strategy-research/references/execution-semantics.md +107 -0
  233. package/skills/okx-strategy-research/references/field-traps.md +142 -0
  234. package/skills/okx-strategy-research/references/python-api.md +121 -0
  235. package/skills/okx-strategy-research/references/tools-and-data.md +192 -0
  236. package/skills/okx-trading/SKILL.md +11 -10
@@ -0,0 +1,158 @@
1
+ """Strategy decisions.
2
+
3
+ A decision is an *intent*, never an order. It carries no contract count: the
4
+ host derives a legal size from its own budget and the instrument's rules, so a
5
+ strategy cannot size a position past the risk boundary.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+
12
+ NO_ACTION = "no_action"
13
+ OPEN_LONG = "open_long"
14
+ OPEN_SHORT = "open_short"
15
+ CLOSE_LONG = "close_long"
16
+ CLOSE_SHORT = "close_short"
17
+ CANCEL_ORDER = "cancel_order"
18
+
19
+ OPENING_ACTIONS = frozenset({OPEN_LONG, OPEN_SHORT})
20
+ CLOSING_ACTIONS = frozenset({CLOSE_LONG, CLOSE_SHORT})
21
+ ALL_ACTIONS = frozenset({NO_ACTION, *OPENING_ACTIONS, *CLOSING_ACTIONS, CANCEL_ORDER})
22
+
23
+ MARKET = "market"
24
+ LIMIT = "limit"
25
+
26
+
27
+ class StrategyError(Exception):
28
+ """Raised when strategy output violates the decision contract."""
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class Execution:
33
+ order_type: str
34
+ limit_price: float | None = None
35
+
36
+ def as_dict(self) -> dict[str, object]:
37
+ if self.order_type == MARKET:
38
+ return {"orderType": MARKET}
39
+ return {"orderType": LIMIT, "limitPrice": self.limit_price}
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class Protection:
44
+ """An optional *request* attached to an opening action, not an order."""
45
+
46
+ stop_loss_price: float | None = None
47
+ take_profit_price: float | None = None
48
+
49
+ def as_dict(self) -> dict[str, object]:
50
+ payload: dict[str, object] = {}
51
+ if self.stop_loss_price is not None:
52
+ payload["stopLossPrice"] = self.stop_loss_price
53
+ if self.take_profit_price is not None:
54
+ payload["takeProfitPrice"] = self.take_profit_price
55
+ return payload
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class Decision:
60
+ action: str
61
+ reason: str
62
+ execution: Execution = field(default_factory=lambda: Execution(MARKET))
63
+ protection: Protection | None = None
64
+ order_id: str | None = None
65
+
66
+ def as_dict(self) -> dict[str, object]:
67
+ payload: dict[str, object] = {"action": self.action, "reason": self.reason}
68
+ if self.action in OPENING_ACTIONS or self.action in CLOSING_ACTIONS:
69
+ payload["execution"] = self.execution.as_dict()
70
+ if self.protection is not None:
71
+ protection = self.protection.as_dict()
72
+ if protection:
73
+ payload["protection"] = protection
74
+ if self.order_id is not None:
75
+ payload["orderId"] = self.order_id
76
+ return payload
77
+
78
+
79
+ def market_order() -> Execution:
80
+ return Execution(MARKET)
81
+
82
+
83
+ def limit_order(limit_price: float) -> Execution:
84
+ price = _positive_price(limit_price, "limit_price")
85
+ return Execution(LIMIT, price)
86
+
87
+
88
+ def _positive_price(value: object, label: str) -> float:
89
+ try:
90
+ price = float(value) # type: ignore[arg-type]
91
+ except (TypeError, ValueError) as error:
92
+ raise StrategyError(f"{label} must be a number") from error
93
+ if price != price or price in (float("inf"), float("-inf")):
94
+ raise StrategyError(f"{label} must be finite")
95
+ if price <= 0:
96
+ raise StrategyError(f"{label} must be greater than zero")
97
+ return price
98
+
99
+
100
+ def _reason(value: object) -> str:
101
+ if not isinstance(value, str) or not value.strip():
102
+ raise StrategyError("Every decision requires a non-empty audit reason")
103
+ if len(value) > 500:
104
+ raise StrategyError("A decision reason must be at most 500 characters")
105
+ return value.strip()
106
+
107
+
108
+ def _protection(value: object) -> Protection | None:
109
+ """Accepts the documented mapping form: ``{"stopLossPrice": .., "takeProfitPrice": ..}``."""
110
+ if value is None:
111
+ return None
112
+ if not isinstance(value, dict):
113
+ raise StrategyError("protection must be a mapping with stopLossPrice and/or takeProfitPrice")
114
+ unknown = set(value) - {"stopLossPrice", "takeProfitPrice"}
115
+ if unknown:
116
+ raise StrategyError(f"Unknown protection field(s): {', '.join(sorted(unknown))}")
117
+ stop = value.get("stopLossPrice")
118
+ take = value.get("takeProfitPrice")
119
+ if stop is None and take is None:
120
+ raise StrategyError("protection requires stopLossPrice and/or takeProfitPrice")
121
+ return Protection(
122
+ stop_loss_price=None if stop is None else _positive_price(stop, "stopLossPrice"),
123
+ take_profit_price=None if take is None else _positive_price(take, "takeProfitPrice"),
124
+ )
125
+
126
+
127
+ def _execution(value: object) -> Execution:
128
+ if value is None:
129
+ return Execution(MARKET)
130
+ if isinstance(value, Execution):
131
+ return value
132
+ raise StrategyError("execution must be ctx.market_order() or ctx.limit_order(price)")
133
+
134
+
135
+ def no_action(reason: object = "no action") -> Decision:
136
+ return Decision(NO_ACTION, _reason(reason))
137
+
138
+
139
+ def open_long(reason: object, protection: object = None, execution: object = None) -> Decision:
140
+ return Decision(OPEN_LONG, _reason(reason), _execution(execution), _protection(protection))
141
+
142
+
143
+ def open_short(reason: object, protection: object = None, execution: object = None) -> Decision:
144
+ return Decision(OPEN_SHORT, _reason(reason), _execution(execution), _protection(protection))
145
+
146
+
147
+ def close_long(reason: object, execution: object = None) -> Decision:
148
+ return Decision(CLOSE_LONG, _reason(reason), _execution(execution))
149
+
150
+
151
+ def close_short(reason: object, execution: object = None) -> Decision:
152
+ return Decision(CLOSE_SHORT, _reason(reason), _execution(execution))
153
+
154
+
155
+ def cancel_order(order_id: object, reason: object) -> Decision:
156
+ if not isinstance(order_id, str) or not order_id.strip():
157
+ raise StrategyError("cancel_order requires the id of a current open order")
158
+ return Decision(CANCEL_ORDER, _reason(reason), order_id=order_id.strip())
@@ -0,0 +1,164 @@
1
+ """The immutable context handed to a strategy handler.
2
+
3
+ Every field is read-only and bounded by ``as_of_ms``. The market view holds the
4
+ full bar array but exposes only the visible prefix, so look-ahead is prevented
5
+ structurally rather than by convention.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from types import MappingProxyType
11
+ from typing import Mapping
12
+
13
+ from . import actions
14
+ from .actions import Decision, Execution
15
+ from .indicators import IndicatorCache, IndicatorView
16
+ from .portfolio import PortfolioView
17
+ from .timeframe import Bar, TimeframeAggregator, interval_ms
18
+
19
+
20
+ class ContextError(Exception):
21
+ """Raised when a strategy requests data outside its contract."""
22
+
23
+
24
+ class MarketView:
25
+ """Point-in-time K-line access across derived timeframes."""
26
+
27
+ __slots__ = ("_aggregates", "_instrument_id", "_minutes", "_visible")
28
+
29
+ def __init__(
30
+ self,
31
+ instrument_id: str,
32
+ minutes: list[Bar],
33
+ visible: int,
34
+ aggregates: Mapping[str, tuple[Bar, ...]],
35
+ ) -> None:
36
+ self._instrument_id = instrument_id
37
+ self._minutes = minutes
38
+ self._visible = visible
39
+ self._aggregates = aggregates
40
+
41
+ def bars(self, instrument_id: str, interval: str = "1m", lookback: int | None = None) -> tuple[Bar, ...]:
42
+ if instrument_id != self._instrument_id:
43
+ raise ContextError(
44
+ f"Market data is available for {self._instrument_id}, not {instrument_id}"
45
+ )
46
+ interval_ms(interval)
47
+ if interval == "1m":
48
+ series: tuple[Bar, ...] = tuple(self._minutes[: self._visible])
49
+ else:
50
+ found = self._aggregates.get(interval)
51
+ if found is None:
52
+ raise ContextError(f"Series '{interval}' is not available in this run")
53
+ series = found
54
+ if lookback is None:
55
+ return series
56
+ if not isinstance(lookback, int) or isinstance(lookback, bool) or lookback <= 0:
57
+ raise ValueError("lookback must be a positive integer")
58
+ return series[-lookback:]
59
+
60
+
61
+ class StrategyContext:
62
+ """Read-only decision context for one cutoff."""
63
+
64
+ __slots__ = (
65
+ "_frozen",
66
+ "as_of_ms",
67
+ "bar",
68
+ "indicators",
69
+ "instrument_id",
70
+ "interval",
71
+ "kind",
72
+ "market",
73
+ "params",
74
+ "portfolio",
75
+ )
76
+
77
+ def __init__(
78
+ self,
79
+ *,
80
+ kind: str,
81
+ as_of_ms: int,
82
+ instrument_id: str,
83
+ interval: str,
84
+ bar: Bar | None,
85
+ market: MarketView,
86
+ portfolio: PortfolioView,
87
+ params: Mapping[str, object],
88
+ indicators: IndicatorView,
89
+ ) -> None:
90
+ object.__setattr__(self, "kind", kind)
91
+ object.__setattr__(self, "as_of_ms", as_of_ms)
92
+ object.__setattr__(self, "instrument_id", instrument_id)
93
+ object.__setattr__(self, "interval", interval)
94
+ object.__setattr__(self, "bar", bar)
95
+ object.__setattr__(self, "market", market)
96
+ object.__setattr__(self, "portfolio", portfolio)
97
+ object.__setattr__(self, "params", params)
98
+ object.__setattr__(self, "indicators", indicators)
99
+ object.__setattr__(self, "_frozen", True)
100
+
101
+ def __setattr__(self, name: str, value: object) -> None:
102
+ raise ContextError("The strategy context is read-only")
103
+
104
+ def __delattr__(self, name: str) -> None:
105
+ raise ContextError("The strategy context is read-only")
106
+
107
+ # Position lookup shortcut, matching `portfolio.position`.
108
+ def position(self, instrument_id: str, side: str): # noqa: ANN201 - returns Position | None
109
+ return self.portfolio.position(instrument_id, side)
110
+
111
+ def market_order(self) -> Execution:
112
+ return actions.market_order()
113
+
114
+ def limit_order(self, limit_price: float) -> Execution:
115
+ return actions.limit_order(limit_price)
116
+
117
+ def no_action(self, reason: object = "no action") -> Decision:
118
+ return actions.no_action(reason)
119
+
120
+ def open_long(self, reason: object, protection: object = None, execution: object = None) -> Decision:
121
+ return actions.open_long(reason, protection, execution)
122
+
123
+ def open_short(self, reason: object, protection: object = None, execution: object = None) -> Decision:
124
+ return actions.open_short(reason, protection, execution)
125
+
126
+ def close_long(self, reason: object, execution: object = None) -> Decision:
127
+ return actions.close_long(reason, execution)
128
+
129
+ def close_short(self, reason: object, execution: object = None) -> Decision:
130
+ return actions.close_short(reason, execution)
131
+
132
+ def cancel_order(self, order_id: object, reason: object) -> Decision:
133
+ return actions.cancel_order(order_id, reason)
134
+
135
+
136
+ def immutable_params(params: Mapping[str, object] | None) -> Mapping[str, object]:
137
+ return MappingProxyType(dict(params or {}))
138
+
139
+
140
+ class SeriesSet:
141
+ """Maintains one aggregator per requested higher timeframe."""
142
+
143
+ __slots__ = ("_aggregators", "_cache", "_instrument_id")
144
+
145
+ def __init__(self, instrument_id: str, intervals: list[str]) -> None:
146
+ self._instrument_id = instrument_id
147
+ self._aggregators = {
148
+ interval: TimeframeAggregator(interval)
149
+ for interval in intervals
150
+ if interval != "1m"
151
+ }
152
+ self._cache = IndicatorCache()
153
+
154
+ @property
155
+ def indicators(self) -> IndicatorCache:
156
+ return self._cache
157
+
158
+ def push(self, bar: Bar) -> None:
159
+ self._cache.append(bar)
160
+ for aggregator in self._aggregators.values():
161
+ aggregator.push(bar)
162
+
163
+ def snapshot(self) -> dict[str, tuple[Bar, ...]]:
164
+ return {interval: agg.snapshot() for interval, agg in self._aggregators.items()}