@bitget-ai/getagent-skill 0.2.1
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/.claude-plugin/marketplace.json +28 -0
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +99 -0
- package/VERSION +1 -0
- package/bin/getagent-skill.js +140 -0
- package/package.json +45 -0
- package/skills/getagent/SKILL.md +129 -0
- package/skills/getagent/examples/btc-ema-cross-demo/README.md +61 -0
- package/skills/getagent/examples/btc-ema-cross-demo/backtest.yaml +33 -0
- package/skills/getagent/examples/btc-ema-cross-demo/manifest.yaml +94 -0
- package/skills/getagent/examples/btc-ema-cross-demo/src/main.py +88 -0
- package/skills/getagent/examples/btc-ema-cross-demo/src/strategy.py +118 -0
- package/skills/getagent/references/api/enable.md +95 -0
- package/skills/getagent/references/api/error-responses.md +77 -0
- package/skills/getagent/references/api/index.md +38 -0
- package/skills/getagent/references/api/list.md +80 -0
- package/skills/getagent/references/api/my-playbooks.md +41 -0
- package/skills/getagent/references/api/publish.md +76 -0
- package/skills/getagent/references/api/run.md +149 -0
- package/skills/getagent/references/api/upload.md +76 -0
- package/skills/getagent/references/backtest-engine.md +438 -0
- package/skills/getagent/references/package-schema.md +552 -0
- package/skills/getagent/references/sandbox-runtime.md +201 -0
- package/skills/getagent/references/sdk/backtest/catalog.md +208 -0
- package/skills/getagent/references/sdk/data/arxiv.md +41 -0
- package/skills/getagent/references/sdk/data/catalog.md +56 -0
- package/skills/getagent/references/sdk/data/commodity.md +226 -0
- package/skills/getagent/references/sdk/data/coverage.md +82 -0
- package/skills/getagent/references/sdk/data/crypto.md +2906 -0
- package/skills/getagent/references/sdk/data/currency.md +123 -0
- package/skills/getagent/references/sdk/data/derivatives.md +269 -0
- package/skills/getagent/references/sdk/data/economy.md +1348 -0
- package/skills/getagent/references/sdk/data/equity.md +2120 -0
- package/skills/getagent/references/sdk/data/etf.md +372 -0
- package/skills/getagent/references/sdk/data/famafrench.md +201 -0
- package/skills/getagent/references/sdk/data/fixedincome.md +804 -0
- package/skills/getagent/references/sdk/data/imf_utils.md +225 -0
- package/skills/getagent/references/sdk/data/index.md +216 -0
- package/skills/getagent/references/sdk/data/news.md +149 -0
- package/skills/getagent/references/sdk/data/playbook-supported.md +9871 -0
- package/skills/getagent/references/sdk/data/regulators.md +299 -0
- package/skills/getagent/references/sdk/data/sentiment.md +323 -0
- package/skills/getagent/references/sdk/data/uscongress.md +126 -0
- package/skills/getagent/references/sdk/data/web_search.md +68 -0
- package/skills/getagent/references/sdk/data/wikipedia.md +97 -0
- package/skills/getagent/references/sdk/llm/catalog.md +117 -0
- package/skills/getagent/references/sdk/runtime/catalog.md +195 -0
- package/skills/getagent/references/sdk/trade/account.md +61 -0
- package/skills/getagent/references/sdk/trade/catalog.md +35 -0
- package/skills/getagent/references/sdk/trade/contract.md +331 -0
- package/skills/getagent/references/sdk/trade/helpers.md +466 -0
- package/skills/getagent/references/sdk/trade/market.md +28 -0
- package/skills/getagent/references/sdk/trade/patterns.md +102 -0
- package/skills/getagent/references/sdk/trade/spot.md +165 -0
- package/skills/getagent/references/sdk.md +198 -0
- package/skills/getagent/scripts/validate.py +965 -0
- package/skills/getagent/scripts/version_check.sh +62 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Entry point for the BTC EMA Crossover Demo Playbook.
|
|
2
|
+
|
|
3
|
+
For backtest_support: full playbooks, the platform injects
|
|
4
|
+
runtime.evaluation_mode="historical" on /api/v1/playbook/run. We only handle
|
|
5
|
+
that path here; live cron is not configured for this demo.
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from getagent import backtest, data, runtime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _sanitize(value: Any) -> Any:
|
|
14
|
+
if isinstance(value, float) and not math.isfinite(value):
|
|
15
|
+
return None
|
|
16
|
+
return value
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _sanitize_metrics(metrics: dict[str, Any]) -> dict[str, Any]:
|
|
20
|
+
return {key: _sanitize(val) for key, val in metrics.items()}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run() -> None:
|
|
24
|
+
cfg = runtime.manifest.get("strategy_config", {}) or {}
|
|
25
|
+
symbols = cfg.get("trading_symbols") or ["BTCUSDT"]
|
|
26
|
+
symbol = symbols[0]
|
|
27
|
+
|
|
28
|
+
bars = data.crypto.futures.kline(
|
|
29
|
+
symbol=symbol,
|
|
30
|
+
interval="1h",
|
|
31
|
+
limit=1000,
|
|
32
|
+
)
|
|
33
|
+
replay_frame = backtest.prepare_frame(bars, datetime_index="date")
|
|
34
|
+
|
|
35
|
+
if replay_frame.empty:
|
|
36
|
+
runtime.emit_signal(
|
|
37
|
+
action="watch",
|
|
38
|
+
symbol=symbol,
|
|
39
|
+
confidence=0.0,
|
|
40
|
+
metrics={"rows": 0},
|
|
41
|
+
meta={"reason": "no historical bars returned"},
|
|
42
|
+
)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
instrument_key = f"{symbol}.BINANCE"
|
|
46
|
+
result = backtest.run(
|
|
47
|
+
ohlcv_data={instrument_key: replay_frame},
|
|
48
|
+
spec=runtime.backtest_spec,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
chart_path = backtest.generate_chart(result)
|
|
52
|
+
summary = result.summary or {}
|
|
53
|
+
net_pnl_raw = summary.get("net_pnl", 0)
|
|
54
|
+
try:
|
|
55
|
+
net_pnl = float(net_pnl_raw or 0)
|
|
56
|
+
except (TypeError, ValueError):
|
|
57
|
+
net_pnl = 0.0
|
|
58
|
+
|
|
59
|
+
action = "long" if net_pnl > 0 else "watch"
|
|
60
|
+
metrics = _sanitize_metrics(
|
|
61
|
+
{
|
|
62
|
+
"total_return_pct": result.total_return_pct,
|
|
63
|
+
"net_pnl": net_pnl,
|
|
64
|
+
"starting_balance": summary.get("starting_balance"),
|
|
65
|
+
"sharpe_ratio": result.sharpe_ratio,
|
|
66
|
+
"max_drawdown_pct": result.max_drawdown_pct,
|
|
67
|
+
"win_rate": result.win_rate,
|
|
68
|
+
"total_trades": result.total_trades,
|
|
69
|
+
"profit_factor": result.profit_factor,
|
|
70
|
+
"rows": len(replay_frame),
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
runtime.emit_signal(
|
|
75
|
+
action=action,
|
|
76
|
+
symbol=symbol,
|
|
77
|
+
confidence=_sanitize(result.win_rate) or 0.0,
|
|
78
|
+
metrics=metrics,
|
|
79
|
+
meta={
|
|
80
|
+
"chart_path": chart_path,
|
|
81
|
+
"fast_period": cfg.get("fast_period"),
|
|
82
|
+
"slow_period": cfg.get("slow_period"),
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
run()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from nautilus_trader.config import StrategyConfig
|
|
5
|
+
from nautilus_trader.model.data import Bar, BarType
|
|
6
|
+
from nautilus_trader.model.enums import OrderSide, TimeInForce
|
|
7
|
+
from nautilus_trader.model.identifiers import InstrumentId
|
|
8
|
+
from nautilus_trader.model.instruments import Instrument
|
|
9
|
+
from nautilus_trader.model.objects import Quantity
|
|
10
|
+
from nautilus_trader.trading.strategy import Strategy
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EmaCrossStrategyConfig(StrategyConfig):
|
|
14
|
+
instrument_id: Optional[InstrumentId] = None
|
|
15
|
+
bar_type: Optional[BarType] = None
|
|
16
|
+
instrument_ids: tuple[InstrumentId, ...] = ()
|
|
17
|
+
bar_types: tuple[BarType, ...] = ()
|
|
18
|
+
trade_size: str = "0.01"
|
|
19
|
+
fast_period: int = 12
|
|
20
|
+
slow_period: int = 26
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EmaCrossStrategy(Strategy):
|
|
24
|
+
def __init__(self, config: EmaCrossStrategyConfig) -> None:
|
|
25
|
+
super().__init__(config)
|
|
26
|
+
self.cfg = config
|
|
27
|
+
self._closes: list[float] = []
|
|
28
|
+
self._fast_ema: Optional[float] = None
|
|
29
|
+
self._slow_ema: Optional[float] = None
|
|
30
|
+
self._prev_diff: Optional[float] = None
|
|
31
|
+
self._position: str = "NONE"
|
|
32
|
+
self._instrument: Optional[Instrument] = None
|
|
33
|
+
|
|
34
|
+
def on_start(self) -> None:
|
|
35
|
+
bar_type = self.cfg.bar_type or (
|
|
36
|
+
self.cfg.bar_types[0] if self.cfg.bar_types else None
|
|
37
|
+
)
|
|
38
|
+
instrument_id = self.cfg.instrument_id or (
|
|
39
|
+
self.cfg.instrument_ids[0] if self.cfg.instrument_ids else None
|
|
40
|
+
)
|
|
41
|
+
if bar_type is None or instrument_id is None:
|
|
42
|
+
raise RuntimeError("bar_type and instrument_id must be set")
|
|
43
|
+
self._instrument = self.cache.instrument(instrument_id)
|
|
44
|
+
self.subscribe_bars(bar_type)
|
|
45
|
+
|
|
46
|
+
def on_bar(self, bar: Bar) -> None:
|
|
47
|
+
close = float(bar.close)
|
|
48
|
+
self._closes.append(close)
|
|
49
|
+
|
|
50
|
+
# Need enough warm-up before either EMA is meaningful.
|
|
51
|
+
warmup = max(self.cfg.slow_period, self.cfg.fast_period) + 1
|
|
52
|
+
if len(self._closes) < warmup:
|
|
53
|
+
self._fast_ema = self._update_ema(self._fast_ema, close, self.cfg.fast_period)
|
|
54
|
+
self._slow_ema = self._update_ema(self._slow_ema, close, self.cfg.slow_period)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
self._fast_ema = self._update_ema(self._fast_ema, close, self.cfg.fast_period)
|
|
58
|
+
self._slow_ema = self._update_ema(self._slow_ema, close, self.cfg.slow_period)
|
|
59
|
+
|
|
60
|
+
diff = self._fast_ema - self._slow_ema # type: ignore[operator]
|
|
61
|
+
if self._prev_diff is None:
|
|
62
|
+
self._prev_diff = diff
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
cross_up = self._prev_diff <= 0.0 < diff
|
|
66
|
+
cross_down = self._prev_diff >= 0.0 > diff
|
|
67
|
+
self._prev_diff = diff
|
|
68
|
+
|
|
69
|
+
instrument = self._instrument
|
|
70
|
+
if instrument is None:
|
|
71
|
+
return
|
|
72
|
+
qty = Quantity(Decimal(self.cfg.trade_size), instrument.size_precision)
|
|
73
|
+
|
|
74
|
+
if self._position == "NONE":
|
|
75
|
+
if cross_up:
|
|
76
|
+
self._submit(instrument.id, OrderSide.BUY, qty)
|
|
77
|
+
self._position = "LONG"
|
|
78
|
+
elif cross_down:
|
|
79
|
+
self._submit(instrument.id, OrderSide.SELL, qty)
|
|
80
|
+
self._position = "SHORT"
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
if self._position == "LONG" and cross_down:
|
|
84
|
+
self._close_open(instrument.id, OrderSide.SELL)
|
|
85
|
+
self._position = "NONE"
|
|
86
|
+
elif self._position == "SHORT" and cross_up:
|
|
87
|
+
self._close_open(instrument.id, OrderSide.BUY)
|
|
88
|
+
self._position = "NONE"
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _update_ema(prev: Optional[float], value: float, period: int) -> float:
|
|
92
|
+
if prev is None:
|
|
93
|
+
return value
|
|
94
|
+
alpha = 2.0 / (period + 1)
|
|
95
|
+
return alpha * value + (1.0 - alpha) * prev
|
|
96
|
+
|
|
97
|
+
def _submit(
|
|
98
|
+
self,
|
|
99
|
+
instrument_id: InstrumentId,
|
|
100
|
+
side: OrderSide,
|
|
101
|
+
quantity: Quantity,
|
|
102
|
+
) -> None:
|
|
103
|
+
order = self.order_factory.market(
|
|
104
|
+
instrument_id=instrument_id,
|
|
105
|
+
order_side=side,
|
|
106
|
+
quantity=quantity,
|
|
107
|
+
time_in_force=TimeInForce.GTC,
|
|
108
|
+
)
|
|
109
|
+
self.submit_order(order)
|
|
110
|
+
|
|
111
|
+
def _close_open(self, instrument_id: InstrumentId, side: OrderSide) -> None:
|
|
112
|
+
for position in self.cache.positions_open(instrument_id=instrument_id):
|
|
113
|
+
self._submit(instrument_id, side, position.quantity)
|
|
114
|
+
|
|
115
|
+
def on_stop(self) -> None:
|
|
116
|
+
if self._instrument is not None:
|
|
117
|
+
self.cancel_all_orders(self._instrument.id)
|
|
118
|
+
self.close_all_positions(self._instrument.id)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Enable / Disable Playbook
|
|
2
|
+
|
|
3
|
+
Subscribe or unsubscribe a user from a published Playbook.
|
|
4
|
+
|
|
5
|
+
Subscribing deploys a published Playbook version for one user. It does not
|
|
6
|
+
create a new strategy, a new public version, or a private package fork.
|
|
7
|
+
|
|
8
|
+
## `POST /api/v1/playbook/enable`
|
|
9
|
+
|
|
10
|
+
**Auth**: `ACCESS-KEY` header from the Bitget OpenAPI credential. Missing it returns 401.
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"version_id": "version-...",
|
|
15
|
+
"chat_id": "123456789",
|
|
16
|
+
"channel": "telegram",
|
|
17
|
+
"schedule_timezone": "Asia/Shanghai"
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Enables the published version for the current user and stores the Telegram
|
|
22
|
+
`chat_id` used for signal delivery.
|
|
23
|
+
|
|
24
|
+
**Important:** `chat_id` is required for Telegram delivery.
|
|
25
|
+
`schedule_timezone` is optional. When omitted, the server uses the user's
|
|
26
|
+
profile timezone, then `manifest.schedule.tz`, then `Asia/Shanghai`.
|
|
27
|
+
|
|
28
|
+
### Success Response
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"instance_id": "inst-...",
|
|
33
|
+
"strategy_id": "strategy-...",
|
|
34
|
+
"version_id": "version-...",
|
|
35
|
+
"schedule_tz": "Asia/Shanghai",
|
|
36
|
+
"status": "active"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Subscription Semantics
|
|
41
|
+
|
|
42
|
+
Subscribing means:
|
|
43
|
+
|
|
44
|
+
- a `PlaybookInstance` points at the immutable published `version_id`
|
|
45
|
+
- per-user `config_overrides` are stored on the deployment instance
|
|
46
|
+
- a personal reminder named `playbook-instance:{instance_id}` drives execution
|
|
47
|
+
- the effective schedule timezone is stored on the instance
|
|
48
|
+
- future executions run with that subscriber's own trading context
|
|
49
|
+
- any trade-capable behavior still respects the Playbook's published guard mode
|
|
50
|
+
- published Playbooks with `runtime_profile: deterministic` can be enabled
|
|
51
|
+
- published Playbooks with `runtime_profile: llm_bounded` can also be enabled when the deployment has managed Playbook LLM runtime enabled
|
|
52
|
+
|
|
53
|
+
Subscription does **not** mean:
|
|
54
|
+
|
|
55
|
+
- enabling automatic follow trading without the user's explicit mode choice
|
|
56
|
+
- forcing a live-only strategy to pretend it has historical backtests
|
|
57
|
+
- granting users permission to edit the Playbook's core logic
|
|
58
|
+
- creating another Playbook strategy or version just because parameters changed
|
|
59
|
+
|
|
60
|
+
## `POST /api/v1/playbook/disable`
|
|
61
|
+
|
|
62
|
+
**Auth**: `ACCESS-KEY` header from the Bitget OpenAPI credential. Missing it returns 401.
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"instance_id": "inst-..."
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Disables one deployed subscription instance so the user stops receiving signals.
|
|
71
|
+
The server also disables that instance's personal reminder.
|
|
72
|
+
|
|
73
|
+
### Disable Response
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"instance_id": "inst-...",
|
|
78
|
+
"strategy_id": "strategy-...",
|
|
79
|
+
"version_id": "version-...",
|
|
80
|
+
"status": "disabled"
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Auto-trading Note
|
|
85
|
+
|
|
86
|
+
If a Playbook advertises automated trading support, the effective behavior still
|
|
87
|
+
depends on its published metadata:
|
|
88
|
+
|
|
89
|
+
- `execution_mode`
|
|
90
|
+
- `follow_trade_supported`
|
|
91
|
+
|
|
92
|
+
Recommended defaults:
|
|
93
|
+
|
|
94
|
+
- replayable strategies can opt into `follow_trade` when follow trading is supported
|
|
95
|
+
- live-only strategies should default to `signal_only`
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Error Responses
|
|
2
|
+
|
|
3
|
+
Error response format for all Playbook API endpoints.
|
|
4
|
+
|
|
5
|
+
## Standard Format
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"detail": "Error description"
|
|
10
|
+
}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or with structured error list:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"detail": {
|
|
18
|
+
"errors": [
|
|
19
|
+
"manifest.yaml: missing 'name'",
|
|
20
|
+
"src/main.py: blocked import 'requests' (line 3)"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Authentication Contract
|
|
27
|
+
|
|
28
|
+
Every authenticated Playbook endpoint on the prod OpenAPI gateway requires the following header. Missing it returns 401:
|
|
29
|
+
|
|
30
|
+
- `ACCESS-KEY` — Bitget OpenAPI access key for the caller
|
|
31
|
+
|
|
32
|
+
`X-User-Id`, `X-Api-Key`, and `X-Principal-Id` are **not** prod OpenAPI client credentials here. The gateway resolves the caller from `ACCESS-KEY`; do not use backend-internal identity headers in client code.
|
|
33
|
+
|
|
34
|
+
If an access key is revoked or cannot resolve to an active principal, authenticated endpoints return 403.
|
|
35
|
+
|
|
36
|
+
## HTTP Status Codes
|
|
37
|
+
|
|
38
|
+
| Status | Meaning | Common Scenario |
|
|
39
|
+
|--------|---------|-----------------|
|
|
40
|
+
| 200 | Success | Normal response |
|
|
41
|
+
| 401 | Unauthorized | Missing `ACCESS-KEY` header |
|
|
42
|
+
| 403 | Forbidden | Access key has been revoked, principal not found / inactive, or caller is not the resource owner |
|
|
43
|
+
| 404 | Not Found | draft_id or version_id does not exist |
|
|
44
|
+
| 409 | Conflict | Status doesn't allow operation; user already has another active Playbook; Playbook is live-only; or the declared runtime profile is not currently executable |
|
|
45
|
+
| 413 | Payload Too Large | Upload exceeds 10MB |
|
|
46
|
+
| 422 | Validation Failed | Incomplete structure, missing fields, blocked imports, etc. |
|
|
47
|
+
| 503 | Service Unavailable | Sandbox pool not ready |
|
|
48
|
+
|
|
49
|
+
## Validation Error Details (422)
|
|
50
|
+
|
|
51
|
+
The upload endpoint returns 422 for:
|
|
52
|
+
|
|
53
|
+
**Structure**
|
|
54
|
+
- `Missing manifest.yaml`
|
|
55
|
+
- `Missing src/main.py`
|
|
56
|
+
|
|
57
|
+
**Fields**
|
|
58
|
+
- `manifest.yaml: missing 'name'`
|
|
59
|
+
- `manifest.yaml: invalid name 'My Strategy' (must be DNS label format)`
|
|
60
|
+
- `manifest.yaml: decision_mode must be one of ['agentic', 'deterministic', 'llm_assisted']`
|
|
61
|
+
- `manifest.yaml: live-only playbooks cannot default to execution_mode 'follow_trade'`
|
|
62
|
+
- `manifest.yaml: runtime_profile 'llm_bounded' requires backtest_support = 'none'`
|
|
63
|
+
- `backtest.yaml is only allowed when manifest.yaml sets backtest_support = 'full'`
|
|
64
|
+
|
|
65
|
+
**Code**
|
|
66
|
+
- `src/main.py syntax error at line 5: invalid syntax`
|
|
67
|
+
- `src/main.py: blocked import 'requests' (line 3)`
|
|
68
|
+
- `src/main.py: disallowed import 'some_unknown_lib' (line 7)`
|
|
69
|
+
|
|
70
|
+
## Handling Recommendations
|
|
71
|
+
|
|
72
|
+
1. **401** — Make sure `ACCESS-KEY` is sent.
|
|
73
|
+
2. **403 revoked key** — The access key was revoked server-side; mint a new one and retry.
|
|
74
|
+
3. **409 publish status conflict** — Publish only a draft that has the required evidence; do not bump `manifest.version`
|
|
75
|
+
4. **409 backtest/runtime conflict** — Check `backtest_support` and `runtime_profile`. If the strategy is `none` or `llm_bounded`, use paper/live evidence instead of historical backtest claims
|
|
76
|
+
5. **422 validation failed** — Fix each error in the `errors` list and re-upload
|
|
77
|
+
6. **503 sandbox unavailable** — Wait or contact ops
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Playbook API Index
|
|
2
|
+
|
|
3
|
+
This folder documents the HTTP control plane for Playbook packaging,
|
|
4
|
+
publication, execution, and subscription management.
|
|
5
|
+
|
|
6
|
+
Prod OpenAPI base URL: `https://api.bitget.com`.
|
|
7
|
+
Authenticated calls use the `ACCESS-KEY` header.
|
|
8
|
+
|
|
9
|
+
Use these docs after the local package is ready. For Python imports inside
|
|
10
|
+
`src/**`, read [`../sdk.md`](../sdk.md) instead.
|
|
11
|
+
|
|
12
|
+
## Read order
|
|
13
|
+
|
|
14
|
+
1. [`upload.md`](upload.md) — upload a packaged Playbook archive
|
|
15
|
+
2. [`publish.md`](publish.md) — publish a validated Playbook
|
|
16
|
+
3. [`run.md`](run.md) — trigger a manual backtest/evaluation run
|
|
17
|
+
4. [`list.md`](list.md) and `detail` response docs — inspect public Playbooks
|
|
18
|
+
5. [`enable.md`](enable.md) and [`my-playbooks.md`](my-playbooks.md) — manage
|
|
19
|
+
subscriptions
|
|
20
|
+
6. [`error-responses.md`](error-responses.md) — common failure modes
|
|
21
|
+
|
|
22
|
+
## Control-plane docs
|
|
23
|
+
|
|
24
|
+
| Document | Purpose |
|
|
25
|
+
|---|---|
|
|
26
|
+
| [`upload.md`](upload.md) | Request format, package validation, and server-side checks |
|
|
27
|
+
| [`publish.md`](publish.md) | Publish contract, evidence requirements, and 409 cases |
|
|
28
|
+
| [`run.md`](run.md) | Manual run contract and runtime gating |
|
|
29
|
+
| [`list.md`](list.md) | Public list surface |
|
|
30
|
+
| [`enable.md`](enable.md) | Enable/disable subscription execution |
|
|
31
|
+
| [`my-playbooks.md`](my-playbooks.md) | User subscription status |
|
|
32
|
+
| [`error-responses.md`](error-responses.md) | Shared error shapes and examples |
|
|
33
|
+
|
|
34
|
+
## Boundary
|
|
35
|
+
|
|
36
|
+
- `api/` is for HTTP requests to GetAgent cloud services
|
|
37
|
+
- `sdk/` is for Python code running inside the Playbook sandbox
|
|
38
|
+
- Do not confuse `data.crypto.futures.kline(...)` with `/api/v1/playbook/...`
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# List Playbooks
|
|
2
|
+
|
|
3
|
+
List existing Playbooks for creator or consumer flows.
|
|
4
|
+
|
|
5
|
+
## `GET /api/v1/playbook/list`
|
|
6
|
+
|
|
7
|
+
**Auth**: Optional for the default published catalog, required for private states such as `draft`. Authenticated calls to the prod OpenAPI gateway use `ACCESS-KEY`.
|
|
8
|
+
|
|
9
|
+
### Parameters
|
|
10
|
+
|
|
11
|
+
| Param | Type | Required | Description |
|
|
12
|
+
|-------|------|----------|-------------|
|
|
13
|
+
| `status` | string | no | Filter by status. Default: `published`. Options: `draft`, `published`, `deprecated` |
|
|
14
|
+
|
|
15
|
+
### Request Examples
|
|
16
|
+
|
|
17
|
+
Endpoint is the fixed GetAgent prod OpenAPI URL. For authenticated calls, substitute `<access_key>` with the value the user provided in chat.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# List all published Playbooks (no auth)
|
|
21
|
+
curl -s "https://api.bitget.com/api/v1/playbook/list"
|
|
22
|
+
|
|
23
|
+
# List own drafts (auth required)
|
|
24
|
+
curl -s \
|
|
25
|
+
-H "ACCESS-KEY: <access_key>" \
|
|
26
|
+
"https://api.bitget.com/api/v1/playbook/list?status=draft"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Response Shape
|
|
30
|
+
|
|
31
|
+
Published Playbooks should surface enough metadata for public `list / view`
|
|
32
|
+
surfaces to distinguish what kind of evidence the user can trust.
|
|
33
|
+
|
|
34
|
+
Recommended fields:
|
|
35
|
+
|
|
36
|
+
- identity: `strategy_id`, `version_id`, `name`, `display_name`, `version`, `description`
|
|
37
|
+
- public behavior: `decision_mode`, `backtest_support`, `execution_mode`
|
|
38
|
+
- public evidence: `official_evidence_kind`, `official_metrics`
|
|
39
|
+
- subscription hint: `follow_trade_supported`
|
|
40
|
+
|
|
41
|
+
### Success Response
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
[
|
|
45
|
+
{
|
|
46
|
+
"strategy_id": "strategy-...",
|
|
47
|
+
"version_id": "version-...",
|
|
48
|
+
"name": "btc-ema-crossover",
|
|
49
|
+
"display_name": "BTC EMA Crossover",
|
|
50
|
+
"version": "1.0.0",
|
|
51
|
+
"status": "published",
|
|
52
|
+
"description": "Trend following strategy based on EMA 12/26 crossover",
|
|
53
|
+
"trading_symbols": ["BTCUSDT"],
|
|
54
|
+
"tags": ["trend", "ema", "btc"],
|
|
55
|
+
"decision_mode": "deterministic",
|
|
56
|
+
"backtest_support": "full",
|
|
57
|
+
"execution_mode": "follow_trade",
|
|
58
|
+
"follow_trade_supported": true,
|
|
59
|
+
"official_evidence_kind": "backtest",
|
|
60
|
+
"official_metrics": {
|
|
61
|
+
"total_return_pct": 23.5,
|
|
62
|
+
"sharpe_ratio": 1.8,
|
|
63
|
+
"max_drawdown_pct": 8.4,
|
|
64
|
+
"win_rate": 0.62,
|
|
65
|
+
"total_trades": 41
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Public Display Rules
|
|
72
|
+
|
|
73
|
+
- `backtest_support: full`
|
|
74
|
+
- show official backtest summary in list / detail
|
|
75
|
+
- `backtest_support: none`
|
|
76
|
+
- do not show fake Sharpe / win rate / total return
|
|
77
|
+
- show `official_evidence_kind` such as `paper` or `live`
|
|
78
|
+
- explain that the strategy is live-only because the core decision cannot be fairly replayed
|
|
79
|
+
|
|
80
|
+
Returns max 100 results, ordered by creation time descending.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# My Playbooks
|
|
2
|
+
|
|
3
|
+
List the currently enabled Playbook deployments for the authenticated user.
|
|
4
|
+
|
|
5
|
+
## `GET /api/v1/playbook/my-playbooks`
|
|
6
|
+
|
|
7
|
+
**Auth**: `ACCESS-KEY` header from the Bitget OpenAPI credential. Missing it returns 401.
|
|
8
|
+
|
|
9
|
+
### Success Response
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
[
|
|
13
|
+
{
|
|
14
|
+
"instance_id": "e1f2g3-...",
|
|
15
|
+
"strategy_id": "strategy-...",
|
|
16
|
+
"version_id": "version-...",
|
|
17
|
+
"playbook_name": "btc-ema-crossover",
|
|
18
|
+
"display_name": "BTC EMA Crossover",
|
|
19
|
+
"version": "1.0.0",
|
|
20
|
+
"strategy_display_name": "BTC EMA Crossover",
|
|
21
|
+
"version_owner_id": "creator-principal-id",
|
|
22
|
+
"status": "active",
|
|
23
|
+
"execution_mode": "follow_trade",
|
|
24
|
+
"follow_trade_supported": true,
|
|
25
|
+
"channel": "telegram",
|
|
26
|
+
"chat_id": "123456789",
|
|
27
|
+
"reminder_id": "reminder-id"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Returns the caller's active deployments. `strategy_id` is the stable strategy
|
|
33
|
+
tree; `version_id` is the immutable published artifact that scheduled runs
|
|
34
|
+
execute with the instance's `config_overrides`.
|
|
35
|
+
|
|
36
|
+
The list should be enough for Telegram to explain what kind of behavior the
|
|
37
|
+
user enabled:
|
|
38
|
+
|
|
39
|
+
- signal-only
|
|
40
|
+
- manual-confirm execution
|
|
41
|
+
- future automation eligibility
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Publish Playbook
|
|
2
|
+
|
|
3
|
+
Publish a draft Playbook to make it publicly available. Publishing is the only
|
|
4
|
+
step that assigns the formal public version number.
|
|
5
|
+
|
|
6
|
+
## `POST /api/v1/playbook/publish`
|
|
7
|
+
|
|
8
|
+
**Auth**: `ACCESS-KEY` header from the Bitget OpenAPI credential. Missing it returns 401.
|
|
9
|
+
|
|
10
|
+
**Content-Type**: `application/json`
|
|
11
|
+
|
|
12
|
+
### Request
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"draft_id": "draft-...",
|
|
17
|
+
"bump_type": "patch"
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`bump_type` is optional and defaults to `patch`. Valid values are `patch`,
|
|
22
|
+
`minor`, and `major`. The server computes the final semver from the strategy's
|
|
23
|
+
latest published version; clients must not edit `manifest.version` manually.
|
|
24
|
+
|
|
25
|
+
### Success Response
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"strategy_id": "strategy-...",
|
|
30
|
+
"version_id": "version-...",
|
|
31
|
+
"version": "1.0.1",
|
|
32
|
+
"status": "published",
|
|
33
|
+
"published_at": "2026-04-09T12:00:00+00:00"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Permissions
|
|
38
|
+
|
|
39
|
+
Only the Playbook owner can publish. Others receive 403.
|
|
40
|
+
|
|
41
|
+
### Publish Contract
|
|
42
|
+
|
|
43
|
+
Publishing freezes the draft artifact into an immutable version and records its
|
|
44
|
+
parent version within the same strategy tree. A published Playbook should carry a
|
|
45
|
+
stable public contract for:
|
|
46
|
+
|
|
47
|
+
- `decision_mode`
|
|
48
|
+
- `backtest_support`
|
|
49
|
+
- `runtime_profile`
|
|
50
|
+
- `execution_mode`
|
|
51
|
+
- `follow_trade_supported`
|
|
52
|
+
|
|
53
|
+
### Evidence Requirements
|
|
54
|
+
|
|
55
|
+
- If `backtest_support = full`
|
|
56
|
+
- the current runtime must be able to execute it (`runtime_profile = deterministic`)
|
|
57
|
+
- the owner must first produce one successful historical evaluation run
|
|
58
|
+
- the Playbook should expose one official public backtest snapshot
|
|
59
|
+
- list / detail surfaces should use that snapshot, not an arbitrary recent run
|
|
60
|
+
- If `backtest_support = none`
|
|
61
|
+
- `runtime_profile = deterministic` and `runtime_profile = llm_bounded` are both publishable
|
|
62
|
+
- `runtime_profile = llm_bounded` additionally requires deployment-side managed LLM runtime to be enabled
|
|
63
|
+
- do not publish fake Sharpe / win rate / total return claims
|
|
64
|
+
- use paper or live evidence instead
|
|
65
|
+
|
|
66
|
+
Publishing a live-only Playbook is allowed. Publishing misleading historical
|
|
67
|
+
performance is not.
|
|
68
|
+
|
|
69
|
+
### Error Responses
|
|
70
|
+
|
|
71
|
+
| Status | Description |
|
|
72
|
+
|--------|-------------|
|
|
73
|
+
| 401 | Missing `ACCESS-KEY` header |
|
|
74
|
+
| 403 | Not the Playbook owner, access key revoked, or principal not found / inactive |
|
|
75
|
+
| 404 | draft_id not found |
|
|
76
|
+
| 409 | Status is not draft; no successful historical evaluation exists for `backtest_support=full`; or runtime profile is not currently publishable |
|