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.
- package/README.en.md +90 -10
- package/README.md +76 -10
- package/dist/account/private-websocket.js +4 -4
- package/dist/account/private-websocket.js.map +1 -1
- package/dist/account/service.d.ts +12 -1
- package/dist/account/service.js +18 -0
- package/dist/account/service.js.map +1 -1
- package/dist/bars/rate-limiter.d.ts +18 -0
- package/dist/bars/rate-limiter.js +84 -0
- package/dist/bars/rate-limiter.js.map +1 -0
- package/dist/bars/schema.d.ts +36 -0
- package/dist/bars/schema.js +134 -0
- package/dist/bars/schema.js.map +1 -0
- package/dist/bars/service.d.ts +60 -0
- package/dist/bars/service.js +120 -0
- package/dist/bars/service.js.map +1 -0
- package/dist/bars/store.d.ts +105 -0
- package/dist/bars/store.js +415 -0
- package/dist/bars/store.js.map +1 -0
- package/dist/bars/timeframe.d.ts +40 -0
- package/dist/bars/timeframe.js +146 -0
- package/dist/bars/timeframe.js.map +1 -0
- package/dist/bars/types.d.ts +68 -0
- package/dist/bars/types.js +13 -0
- package/dist/bars/types.js.map +1 -0
- package/dist/cli/data-render.d.ts +37 -0
- package/dist/cli/data-render.js +143 -0
- package/dist/cli/data-render.js.map +1 -0
- package/dist/cli/doctor.js +7 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +757 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/live-render.d.ts +24 -0
- package/dist/cli/live-render.js +85 -0
- package/dist/cli/live-render.js.map +1 -0
- package/dist/cli/range.d.ts +28 -0
- package/dist/cli/range.js +63 -0
- package/dist/cli/range.js.map +1 -0
- package/dist/cli/render.js +3 -0
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/strategy-render.d.ts +36 -0
- package/dist/cli/strategy-render.js +391 -0
- package/dist/cli/strategy-render.js.map +1 -0
- package/dist/cli/width.d.ts +18 -0
- package/dist/cli/width.js +71 -0
- package/dist/cli/width.js.map +1 -0
- package/dist/config/loader.js +1 -1
- package/dist/config/schema.d.ts +7 -0
- package/dist/config/schema.js +24 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/okx-client.d.ts +9 -1
- package/dist/core/okx-client.js +14 -5
- package/dist/core/okx-client.js.map +1 -1
- package/dist/i18n/locale.d.ts +24 -0
- package/dist/i18n/locale.js +65 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/messages.d.ts +333 -0
- package/dist/i18n/messages.js +660 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/live/account-snapshot.d.ts +30 -0
- package/dist/live/account-snapshot.js +130 -0
- package/dist/live/account-snapshot.js.map +1 -0
- package/dist/live/cutoff-queue.d.ts +42 -0
- package/dist/live/cutoff-queue.js +69 -0
- package/dist/live/cutoff-queue.js.map +1 -0
- package/dist/live/execution-key.d.ts +23 -0
- package/dist/live/execution-key.js +31 -0
- package/dist/live/execution-key.js.map +1 -0
- package/dist/live/failures.d.ts +37 -0
- package/dist/live/failures.js +57 -0
- package/dist/live/failures.js.map +1 -0
- package/dist/live/gates.d.ts +65 -0
- package/dist/live/gates.js +136 -0
- package/dist/live/gates.js.map +1 -0
- package/dist/live/loop.d.ts +56 -0
- package/dist/live/loop.js +197 -0
- package/dist/live/loop.js.map +1 -0
- package/dist/live/preconditions.d.ts +48 -0
- package/dist/live/preconditions.js +69 -0
- package/dist/live/preconditions.js.map +1 -0
- package/dist/live/reconcile.d.ts +46 -0
- package/dist/live/reconcile.js +104 -0
- package/dist/live/reconcile.js.map +1 -0
- package/dist/live/runner.d.ts +57 -0
- package/dist/live/runner.js +160 -0
- package/dist/live/runner.js.map +1 -0
- package/dist/live/schema.d.ts +18 -0
- package/dist/live/schema.js +91 -0
- package/dist/live/schema.js.map +1 -0
- package/dist/live/service.d.ts +144 -0
- package/dist/live/service.js +303 -0
- package/dist/live/service.js.map +1 -0
- package/dist/live/session.d.ts +85 -0
- package/dist/live/session.js +234 -0
- package/dist/live/session.js.map +1 -0
- package/dist/live/sizing.d.ts +62 -0
- package/dist/live/sizing.js +79 -0
- package/dist/live/sizing.js.map +1 -0
- package/dist/live/store.d.ts +123 -0
- package/dist/live/store.js +350 -0
- package/dist/live/store.js.map +1 -0
- package/dist/live/types.d.ts +82 -0
- package/dist/live/types.js +2 -0
- package/dist/live/types.js.map +1 -0
- package/dist/market/websocket.d.ts +16 -1
- package/dist/market/websocket.js +60 -5
- package/dist/market/websocket.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +15 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/network/connectivity.d.ts +9 -1
- package/dist/network/connectivity.js +28 -1
- package/dist/network/connectivity.js.map +1 -1
- package/dist/report/chart-script.d.ts +12 -0
- package/dist/report/chart-script.js +146 -0
- package/dist/report/chart-script.js.map +1 -0
- package/dist/report/compare-html.d.ts +8 -0
- package/dist/report/compare-html.js +254 -0
- package/dist/report/compare-html.js.map +1 -0
- package/dist/report/compare-script.d.ts +12 -0
- package/dist/report/compare-script.js +109 -0
- package/dist/report/compare-script.js.map +1 -0
- package/dist/report/compare.d.ts +61 -0
- package/dist/report/compare.js +205 -0
- package/dist/report/compare.js.map +1 -0
- package/dist/report/fetch.d.ts +20 -0
- package/dist/report/fetch.js +56 -0
- package/dist/report/fetch.js.map +1 -0
- package/dist/report/html.d.ts +54 -0
- package/dist/report/html.js +641 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/open.d.ts +42 -0
- package/dist/report/open.js +114 -0
- package/dist/report/open.js.map +1 -0
- package/dist/runtime/server.d.ts +16 -1
- package/dist/runtime/server.js +112 -9
- package/dist/runtime/server.js.map +1 -1
- package/dist/setup/installer.d.ts +1 -0
- package/dist/setup/installer.js +8 -0
- package/dist/setup/installer.js.map +1 -1
- package/dist/setup/wizard.js +19 -22
- package/dist/setup/wizard.js.map +1 -1
- package/dist/strategy/constants.d.ts +23 -0
- package/dist/strategy/constants.js +24 -0
- package/dist/strategy/constants.js.map +1 -0
- package/dist/strategy/environment.d.ts +52 -0
- package/dist/strategy/environment.js +187 -0
- package/dist/strategy/environment.js.map +1 -0
- package/dist/strategy/instrument.d.ts +29 -0
- package/dist/strategy/instrument.js +39 -0
- package/dist/strategy/instrument.js.map +1 -0
- package/dist/strategy/optimize.d.ts +73 -0
- package/dist/strategy/optimize.js +113 -0
- package/dist/strategy/optimize.js.map +1 -0
- package/dist/strategy/parameter-space.d.ts +59 -0
- package/dist/strategy/parameter-space.js +221 -0
- package/dist/strategy/parameter-space.js.map +1 -0
- package/dist/strategy/python-bridge.d.ts +24 -0
- package/dist/strategy/python-bridge.js +114 -0
- package/dist/strategy/python-bridge.js.map +1 -0
- package/dist/strategy/schema.d.ts +9 -0
- package/dist/strategy/schema.js +91 -0
- package/dist/strategy/schema.js.map +1 -0
- package/dist/strategy/service.d.ts +138 -0
- package/dist/strategy/service.js +745 -0
- package/dist/strategy/service.js.map +1 -0
- package/dist/strategy/settings.d.ts +162 -0
- package/dist/strategy/settings.js +243 -0
- package/dist/strategy/settings.js.map +1 -0
- package/dist/strategy/store.d.ts +96 -0
- package/dist/strategy/store.js +367 -0
- package/dist/strategy/store.js.map +1 -0
- package/dist/strategy/templates.d.ts +11 -0
- package/dist/strategy/templates.js +134 -0
- package/dist/strategy/templates.js.map +1 -0
- package/dist/strategy/types.d.ts +111 -0
- package/dist/strategy/types.js +2 -0
- package/dist/strategy/types.js.map +1 -0
- package/dist/tools/catalog.d.ts +16 -0
- package/dist/tools/catalog.js +118 -17
- package/dist/tools/catalog.js.map +1 -1
- package/dist/trade/service.d.ts +12 -0
- package/dist/trade/service.js +24 -7
- package/dist/trade/service.js.map +1 -1
- package/dist/tui/app.d.ts +23 -0
- package/dist/tui/app.js +322 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/commands.d.ts +70 -0
- package/dist/tui/commands.js +313 -0
- package/dist/tui/commands.js.map +1 -0
- package/dist/tui/entries.d.ts +17 -0
- package/dist/tui/entries.js +24 -0
- package/dist/tui/entries.js.map +1 -0
- package/dist/tui/execute.d.ts +26 -0
- package/dist/tui/execute.js +664 -0
- package/dist/tui/execute.js.map +1 -0
- package/dist/tui/history.d.ts +17 -0
- package/dist/tui/history.js +48 -0
- package/dist/tui/history.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.js +48 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/line-editor.d.ts +44 -0
- package/dist/tui/line-editor.js +98 -0
- package/dist/tui/line-editor.js.map +1 -0
- package/dist/tui/progress.d.ts +23 -0
- package/dist/tui/progress.js +46 -0
- package/dist/tui/progress.js.map +1 -0
- package/dist/tui/settings-editor.d.ts +18 -0
- package/dist/tui/settings-editor.js +115 -0
- package/dist/tui/settings-editor.js.map +1 -0
- package/docs/live-trading.md +455 -0
- package/docs/strategy-research.md +597 -0
- package/package.json +10 -1
- package/python/desic_strategy/__init__.py +34 -0
- package/python/desic_strategy/actions.py +158 -0
- package/python/desic_strategy/context.py +164 -0
- package/python/desic_strategy/engine.py +614 -0
- package/python/desic_strategy/indicators.py +159 -0
- package/python/desic_strategy/live.py +253 -0
- package/python/desic_strategy/policy.py +193 -0
- package/python/desic_strategy/portfolio.py +152 -0
- package/python/desic_strategy/report.py +319 -0
- package/python/desic_strategy/runner.py +574 -0
- package/python/desic_strategy/timeframe.py +150 -0
- package/python/main.py +18 -0
- package/skills/okx-live-trading/SKILL.md +117 -0
- package/skills/okx-live-trading/agents/openai.yaml +9 -0
- package/skills/okx-live-trading/references/lifecycle.md +128 -0
- package/skills/okx-strategy-research/SKILL.md +113 -0
- package/skills/okx-strategy-research/agents/openai.yaml +9 -0
- package/skills/okx-strategy-research/references/execution-semantics.md +107 -0
- package/skills/okx-strategy-research/references/field-traps.md +142 -0
- package/skills/okx-strategy-research/references/python-api.md +121 -0
- package/skills/okx-strategy-research/references/tools-and-data.md +192 -0
- 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()}
|