trading-cli 0.3.0__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.
- fastbooks_cli/__init__.py +3 -0
- fastbooks_cli/__main__.py +6 -0
- fastbooks_cli/client.py +1169 -0
- fastbooks_cli/commands/__init__.py +0 -0
- fastbooks_cli/commands/apikey.py +144 -0
- fastbooks_cli/commands/backtest.py +450 -0
- fastbooks_cli/commands/bots.py +479 -0
- fastbooks_cli/commands/config.py +294 -0
- fastbooks_cli/commands/dex.py +507 -0
- fastbooks_cli/commands/direct.py +150 -0
- fastbooks_cli/commands/execution.py +298 -0
- fastbooks_cli/commands/init_cmd.py +149 -0
- fastbooks_cli/commands/markets.py +270 -0
- fastbooks_cli/commands/mcp_cmd.py +27 -0
- fastbooks_cli/commands/onchain.py +123 -0
- fastbooks_cli/commands/polymarket.py +2215 -0
- fastbooks_cli/commands/repl.py +385 -0
- fastbooks_cli/commands/strategy.py +259 -0
- fastbooks_cli/commands/swap.py +320 -0
- fastbooks_cli/commands/wallet.py +447 -0
- fastbooks_cli/config_store.py +112 -0
- fastbooks_cli/direct.py +209 -0
- fastbooks_cli/main.py +88 -0
- fastbooks_cli/mcp_server.py +171 -0
- fastbooks_cli/output.py +86 -0
- fastbooks_cli/strategies/__init__.py +87 -0
- trading_cli-0.3.0.dist-info/METADATA +309 -0
- trading_cli-0.3.0.dist-info/RECORD +32 -0
- trading_cli-0.3.0.dist-info/WHEEL +5 -0
- trading_cli-0.3.0.dist-info/entry_points.txt +4 -0
- trading_cli-0.3.0.dist-info/licenses/LICENSE +21 -0
- trading_cli-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Markets command group — orderbooks, tickers, OHLCV, symbols, options."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import csv
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from ..client import FastBooksClient
|
|
11
|
+
from ..output import emit, emit_error, format_table
|
|
12
|
+
|
|
13
|
+
# Safety bounds for --all auto-pagination so a bad backend can't loop forever.
|
|
14
|
+
_ALL_PAGE_SIZE = 1000
|
|
15
|
+
_ALL_MAX_PAGES = 500
|
|
16
|
+
_ALL_MAX_ROWS = 500_000
|
|
17
|
+
|
|
18
|
+
SUPPORTED_EXCHANGES = [
|
|
19
|
+
"binance", "bybit", "okx", "kraken", "coinbase", "kucoin",
|
|
20
|
+
"hyperliquid", "deribit", "polymarket", "limitless",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
TIMEFRAMES = ["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w"]
|
|
24
|
+
|
|
25
|
+
REGIONS = ["us", "ca", "uk", "au", "eu", "jp", "asia"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.group()
|
|
29
|
+
def markets():
|
|
30
|
+
"""Market data: orderbooks, tickers, OHLCV, symbols, options."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ── orderbook ────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
@markets.command()
|
|
37
|
+
@click.argument("exchange", type=click.Choice(SUPPORTED_EXCHANGES, case_sensitive=False))
|
|
38
|
+
@click.argument("symbol")
|
|
39
|
+
@click.pass_context
|
|
40
|
+
def orderbook(ctx, exchange: str, symbol: str):
|
|
41
|
+
"""Fetch orderbook for SYMBOL on EXCHANGE.
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
fastbooks markets orderbook binance BTC/USDT
|
|
45
|
+
fastbooks markets orderbook hyperliquid ETH/USDC
|
|
46
|
+
fastbooks markets orderbook polymarket "Will Trump win?"
|
|
47
|
+
"""
|
|
48
|
+
client: FastBooksClient = ctx.obj
|
|
49
|
+
try:
|
|
50
|
+
data = client.orderbook(exchange, symbol)
|
|
51
|
+
emit(ctx, data, lambda d: _format_orderbook(d))
|
|
52
|
+
except Exception as e:
|
|
53
|
+
emit_error(ctx, str(e))
|
|
54
|
+
ctx.exit(1)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _format_orderbook(data: dict) -> str:
|
|
58
|
+
lines = []
|
|
59
|
+
ob = data if "exchange" in data else data.get("data", data)
|
|
60
|
+
lines.append(f" {ob.get('exchange', '?')} | {ob.get('symbol', '?')}")
|
|
61
|
+
lines.append(f" Mid: {ob.get('midPrice', '?')} Spread: {ob.get('spreadPercent', '?')}%")
|
|
62
|
+
lines.append("")
|
|
63
|
+
|
|
64
|
+
asks = ob.get("asks", [])[:5]
|
|
65
|
+
bids = ob.get("bids", [])[:5]
|
|
66
|
+
lines.append(" ASKS (top 5):")
|
|
67
|
+
for price, size in reversed(asks):
|
|
68
|
+
lines.append(f" {price:>12} {size:>12}")
|
|
69
|
+
lines.append(" ─────────────────────────")
|
|
70
|
+
lines.append(" BIDS (top 5):")
|
|
71
|
+
for price, size in bids:
|
|
72
|
+
lines.append(f" {price:>12} {size:>12}")
|
|
73
|
+
return "\n".join(lines)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ── aggregated orderbooks ────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
@markets.command()
|
|
79
|
+
@click.option("--category", type=click.Choice(["all", "spot", "perpetuals"]), default="all")
|
|
80
|
+
@click.pass_context
|
|
81
|
+
def orderbooks(ctx, category: str):
|
|
82
|
+
"""Fetch aggregated orderbooks across all exchanges.
|
|
83
|
+
|
|
84
|
+
Categories: all, spot, perpetuals.
|
|
85
|
+
"""
|
|
86
|
+
client: FastBooksClient = ctx.obj
|
|
87
|
+
try:
|
|
88
|
+
data = client.orderbooks_aggregated(category)
|
|
89
|
+
emit(ctx, data)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
emit_error(ctx, str(e))
|
|
92
|
+
ctx.exit(1)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ── tickers ──────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
@markets.command()
|
|
98
|
+
@click.option("--category", type=str, default=None, help="Filter: crypto, stocks, forex, dex.")
|
|
99
|
+
@click.option("--exchange", type=str, default=None, help="Filter by exchange name.")
|
|
100
|
+
@click.option("--search", type=str, default=None, help="Search by symbol or name.")
|
|
101
|
+
@click.option("--page", type=int, default=1, help="Page number.")
|
|
102
|
+
@click.option("--limit", type=int, default=50, help="Results per page.")
|
|
103
|
+
@click.option("--all", "fetch_all", is_flag=True, help="Fetch the FULL universe (auto-paginate). Ignores --page.")
|
|
104
|
+
@click.option("--export", "export_path", type=click.Path(dir_okay=False, writable=True), default=None,
|
|
105
|
+
help="Write results to a file (.json or .csv inferred from extension).")
|
|
106
|
+
@click.pass_context
|
|
107
|
+
def tickers(ctx, category, exchange, search, page, limit, fetch_all, export_path):
|
|
108
|
+
"""List tickers across all supported markets.
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
fastbooks markets tickers --category crypto --limit 20
|
|
112
|
+
fastbooks markets tickers --search AAPL
|
|
113
|
+
fastbooks markets tickers --all # entire universe
|
|
114
|
+
fastbooks markets tickers --all --category stocks --export stocks.csv
|
|
115
|
+
fastbooks --json markets tickers --all > universe.json
|
|
116
|
+
"""
|
|
117
|
+
client: FastBooksClient = ctx.obj
|
|
118
|
+
try:
|
|
119
|
+
if fetch_all:
|
|
120
|
+
data = _fetch_all_tickers(client, category=category, exchange=exchange, search=search)
|
|
121
|
+
else:
|
|
122
|
+
data = client.tickers(page=page, limit=limit, category=category, exchange=exchange, search=search)
|
|
123
|
+
|
|
124
|
+
if export_path:
|
|
125
|
+
items = _extract_tickers(data)
|
|
126
|
+
_write_export(items, export_path)
|
|
127
|
+
emit(ctx, {"exported": len(items), "path": export_path},
|
|
128
|
+
lambda d: f" Exported {d['exported']} tickers to {d['path']}")
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
emit(ctx, data, lambda d: _format_tickers(d))
|
|
132
|
+
except Exception as e:
|
|
133
|
+
emit_error(ctx, str(e))
|
|
134
|
+
ctx.exit(1)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _extract_tickers(data) -> list:
|
|
138
|
+
if isinstance(data, list):
|
|
139
|
+
return data
|
|
140
|
+
if isinstance(data, dict):
|
|
141
|
+
items = data.get("tickers", data.get("data", []))
|
|
142
|
+
return items if isinstance(items, list) else []
|
|
143
|
+
return []
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _fetch_all_tickers(client: FastBooksClient, category=None, exchange=None, search=None) -> dict:
|
|
147
|
+
"""Auto-paginate /tickers/all into a single combined result (bounded)."""
|
|
148
|
+
all_items: list = []
|
|
149
|
+
page = 1
|
|
150
|
+
while page <= _ALL_MAX_PAGES and len(all_items) < _ALL_MAX_ROWS:
|
|
151
|
+
resp = client.tickers(page=page, limit=_ALL_PAGE_SIZE, category=category, exchange=exchange, search=search)
|
|
152
|
+
batch = _extract_tickers(resp)
|
|
153
|
+
if not batch:
|
|
154
|
+
break
|
|
155
|
+
all_items.extend(batch)
|
|
156
|
+
if len(batch) < _ALL_PAGE_SIZE:
|
|
157
|
+
break # last page
|
|
158
|
+
page += 1
|
|
159
|
+
return {"tickers": all_items, "total": len(all_items), "pages": page}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
_EXPORT_COLUMNS = ["symbol", "name", "exchange", "category", "price", "change24h", "volume24h"]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _write_export(items: list, path: str) -> None:
|
|
166
|
+
if path.lower().endswith(".csv"):
|
|
167
|
+
with open(path, "w", newline="", encoding="utf-8") as f:
|
|
168
|
+
writer = csv.DictWriter(f, fieldnames=_EXPORT_COLUMNS, extrasaction="ignore")
|
|
169
|
+
writer.writeheader()
|
|
170
|
+
for it in items:
|
|
171
|
+
if isinstance(it, dict):
|
|
172
|
+
writer.writerow({k: it.get(k, "") for k in _EXPORT_COLUMNS})
|
|
173
|
+
else:
|
|
174
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
175
|
+
json.dump(items, f, indent=2, default=str)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _format_tickers(data: dict) -> str:
|
|
179
|
+
items = _extract_tickers(data)
|
|
180
|
+
if not isinstance(items, list):
|
|
181
|
+
return str(data)
|
|
182
|
+
total = data.get("total", len(items)) if isinstance(data, dict) else len(items)
|
|
183
|
+
table = format_table(
|
|
184
|
+
items[:50],
|
|
185
|
+
["symbol", "exchange", "price", "change24h", "volume24h"],
|
|
186
|
+
["Symbol", "Exchange", "Price", "24h Change", "24h Volume"],
|
|
187
|
+
)
|
|
188
|
+
if len(items) > 50:
|
|
189
|
+
table += f"\n … showing 50 of {total} (use --export to save all, or --json for full output)"
|
|
190
|
+
return table
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# ── OHLCV ────────────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
@markets.command()
|
|
196
|
+
@click.argument("exchange", type=str)
|
|
197
|
+
@click.argument("symbol", type=str)
|
|
198
|
+
@click.option("--timeframe", "-t", type=click.Choice(TIMEFRAMES), default="1h", help="Candle timeframe.")
|
|
199
|
+
@click.option("--limit", "-n", type=int, default=50, help="Number of candles.")
|
|
200
|
+
@click.pass_context
|
|
201
|
+
def ohlcv(ctx, exchange: str, symbol: str, timeframe: str, limit: int):
|
|
202
|
+
"""Fetch OHLCV candle data.
|
|
203
|
+
|
|
204
|
+
Examples:
|
|
205
|
+
fastbooks markets ohlcv binance BTC/USDT --timeframe 1d --limit 30
|
|
206
|
+
fastbooks markets ohlcv alpaca AAPL -t 1h -n 100
|
|
207
|
+
"""
|
|
208
|
+
client: FastBooksClient = ctx.obj
|
|
209
|
+
try:
|
|
210
|
+
data = client.ohlcv(exchange, symbol, timeframe, limit)
|
|
211
|
+
emit(ctx, data, lambda d: _format_ohlcv(d))
|
|
212
|
+
except Exception as e:
|
|
213
|
+
emit_error(ctx, str(e))
|
|
214
|
+
ctx.exit(1)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _format_ohlcv(data: dict) -> str:
|
|
218
|
+
bars = data if isinstance(data, list) else data.get("data", data.get("bars", []))
|
|
219
|
+
if not isinstance(bars, list) or not bars:
|
|
220
|
+
return str(data)
|
|
221
|
+
return format_table(
|
|
222
|
+
bars[:20],
|
|
223
|
+
["timestamp", "open", "high", "low", "close", "volume"],
|
|
224
|
+
["Time", "Open", "High", "Low", "Close", "Volume"],
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ── symbols ──────────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
@markets.command()
|
|
231
|
+
@click.option("--region", "-r", type=click.Choice(REGIONS), default="us", help="Market region.")
|
|
232
|
+
@click.option("--sector", "-s", type=str, default=None, help="Filter by sector.")
|
|
233
|
+
@click.option("--limit", "-n", type=int, default=100, help="Max symbols.")
|
|
234
|
+
@click.pass_context
|
|
235
|
+
def symbols(ctx, region: str, sector: str | None, limit: int):
|
|
236
|
+
"""List symbols from the universe.
|
|
237
|
+
|
|
238
|
+
Regions: us, ca, uk, au, eu, jp, asia.
|
|
239
|
+
|
|
240
|
+
Examples:
|
|
241
|
+
fastbooks markets symbols --region us --sector Technology --limit 50
|
|
242
|
+
fastbooks markets symbols -r ca -n 200
|
|
243
|
+
"""
|
|
244
|
+
client: FastBooksClient = ctx.obj
|
|
245
|
+
try:
|
|
246
|
+
data = client.symbols_universe(region, sector, limit)
|
|
247
|
+
emit(ctx, data)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
emit_error(ctx, str(e))
|
|
250
|
+
ctx.exit(1)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ── options chain ────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
@markets.command()
|
|
256
|
+
@click.argument("underlying")
|
|
257
|
+
@click.pass_context
|
|
258
|
+
def options(ctx, underlying: str):
|
|
259
|
+
"""Fetch options chain for UNDERLYING (Deribit).
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
fastbooks markets options BTC
|
|
263
|
+
"""
|
|
264
|
+
client: FastBooksClient = ctx.obj
|
|
265
|
+
try:
|
|
266
|
+
data = client.options_chain(underlying)
|
|
267
|
+
emit(ctx, data)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
emit_error(ctx, str(e))
|
|
270
|
+
ctx.exit(1)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""`mcp` — launch the FastBooks MCP server (stdio) for AI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..output import emit_error
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command("mcp")
|
|
11
|
+
@click.pass_context
|
|
12
|
+
def mcp_cmd(ctx: click.Context):
|
|
13
|
+
"""Start the FastBooks MCP server over stdio (for Claude / MCP-compatible agents).
|
|
14
|
+
|
|
15
|
+
Exposes STANDALONE ccxt tools (100+ exchanges, keyless market data + direct trading)
|
|
16
|
+
plus FastBooks PLATFORM tools (aggregated multi-asset data, the 15-venue execution
|
|
17
|
+
layer, managed bots) when a backend is reachable. Point your agent at this command:
|
|
18
|
+
|
|
19
|
+
{"command": "fastbooks", "args": ["mcp"]} # or the `fastbooks-mcp` script
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
from ..mcp_server import build_server
|
|
23
|
+
except RuntimeError as e: # MCP SDK missing
|
|
24
|
+
emit_error(ctx, str(e))
|
|
25
|
+
ctx.exit(2)
|
|
26
|
+
return
|
|
27
|
+
build_server().run()
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""`onchain` — FastBots on-chain engine (Solana/EVM: Jupiter / Pump.fun / Hyperliquid).
|
|
2
|
+
|
|
3
|
+
Proxies the isolated solana-trading-bots engine via the backend's `/api/onchain/*` route.
|
|
4
|
+
That proxy is auth + email-gated, so these commands need `--token` / FASTBOOKS_API_TOKEN
|
|
5
|
+
(a JWT whose email is on the engine's allowlist).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from ..client import FastBooksClient
|
|
13
|
+
from ..output import emit, emit_error
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def onchain():
|
|
18
|
+
"""FastBots on-chain engine — status, strategies, positions, and live bot control.
|
|
19
|
+
|
|
20
|
+
Real-money on-chain execution on Solana/EVM. Requires --token (JWT on the engine allowlist).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@onchain.command("status")
|
|
25
|
+
@click.pass_context
|
|
26
|
+
def status_cmd(ctx: click.Context):
|
|
27
|
+
"""Live runner status — which on-chain bots are running."""
|
|
28
|
+
client: FastBooksClient = ctx.obj
|
|
29
|
+
try:
|
|
30
|
+
emit(ctx, client.onchain_live_status())
|
|
31
|
+
except Exception as e: # noqa: BLE001
|
|
32
|
+
emit_error(ctx, str(e))
|
|
33
|
+
ctx.exit(1)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@onchain.command("stats")
|
|
37
|
+
@click.option("--by-bot", is_flag=True, help="Per-bot realized PnL breakdown.")
|
|
38
|
+
@click.pass_context
|
|
39
|
+
def stats_cmd(ctx: click.Context, by_bot: bool):
|
|
40
|
+
"""Realized PnL / win-rate stats (global or per bot)."""
|
|
41
|
+
client: FastBooksClient = ctx.obj
|
|
42
|
+
try:
|
|
43
|
+
emit(ctx, client.onchain_stats(by_bot=by_bot))
|
|
44
|
+
except Exception as e: # noqa: BLE001
|
|
45
|
+
emit_error(ctx, str(e))
|
|
46
|
+
ctx.exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@onchain.command("strategies")
|
|
50
|
+
@click.pass_context
|
|
51
|
+
def strategies_cmd(ctx: click.Context):
|
|
52
|
+
"""List deployable on-chain strategies + their params."""
|
|
53
|
+
client: FastBooksClient = ctx.obj
|
|
54
|
+
try:
|
|
55
|
+
emit(ctx, client.onchain_strategies())
|
|
56
|
+
except Exception as e: # noqa: BLE001
|
|
57
|
+
emit_error(ctx, str(e))
|
|
58
|
+
ctx.exit(1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@onchain.command("positions")
|
|
62
|
+
@click.option("--status", default=None, help="open | closed | failed")
|
|
63
|
+
@click.pass_context
|
|
64
|
+
def positions_cmd(ctx: click.Context, status: str | None):
|
|
65
|
+
"""Open/closed on-chain positions."""
|
|
66
|
+
client: FastBooksClient = ctx.obj
|
|
67
|
+
try:
|
|
68
|
+
emit(ctx, client.onchain_positions(status))
|
|
69
|
+
except Exception as e: # noqa: BLE001
|
|
70
|
+
emit_error(ctx, str(e))
|
|
71
|
+
ctx.exit(1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@onchain.command("trades")
|
|
75
|
+
@click.option("--limit", "-n", default=40, type=int)
|
|
76
|
+
@click.pass_context
|
|
77
|
+
def trades_cmd(ctx: click.Context, limit: int):
|
|
78
|
+
"""Recent on-chain trades."""
|
|
79
|
+
client: FastBooksClient = ctx.obj
|
|
80
|
+
try:
|
|
81
|
+
emit(ctx, client.onchain_trades(limit))
|
|
82
|
+
except Exception as e: # noqa: BLE001
|
|
83
|
+
emit_error(ctx, str(e))
|
|
84
|
+
ctx.exit(1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@onchain.command("start")
|
|
88
|
+
@click.argument("strategy_id")
|
|
89
|
+
@click.option("--alloc", type=float, default=None, help="Max SOL allocation for this bot (maxAllocationSol).")
|
|
90
|
+
@click.option("--slippage-bps", type=int, default=None, help="Real swap slippage tolerance (bps).")
|
|
91
|
+
@click.option("--max-safety-risk", type=int, default=None, help="Block entries at/above this risk score (e.g. 40 to allow caution).")
|
|
92
|
+
@click.option("--yes", is_flag=True, help="Skip the real-funds confirmation.")
|
|
93
|
+
@click.pass_context
|
|
94
|
+
def start_cmd(ctx, strategy_id, alloc, slippage_bps, max_safety_risk, yes):
|
|
95
|
+
"""Start a live on-chain bot — REAL FUNDS. e.g. onchain start high_winrate_v2 --alloc 0.5 --max-safety-risk 40"""
|
|
96
|
+
client: FastBooksClient = ctx.obj
|
|
97
|
+
if not yes and not ctx.find_root().params.get("json_output"):
|
|
98
|
+
click.confirm(f"Start LIVE on-chain bot '{strategy_id}' with REAL funds?", abort=True)
|
|
99
|
+
opts: dict = {}
|
|
100
|
+
if alloc is not None:
|
|
101
|
+
opts["maxAllocationSol"] = alloc
|
|
102
|
+
if slippage_bps is not None:
|
|
103
|
+
opts["realSlippageBps"] = slippage_bps
|
|
104
|
+
if max_safety_risk is not None:
|
|
105
|
+
opts["maxSafetyRisk"] = max_safety_risk
|
|
106
|
+
try:
|
|
107
|
+
emit(ctx, client.onchain_live_start(strategy_id, opts))
|
|
108
|
+
except Exception as e: # noqa: BLE001
|
|
109
|
+
emit_error(ctx, str(e))
|
|
110
|
+
ctx.exit(1)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@onchain.command("stop")
|
|
114
|
+
@click.argument("strategy_id")
|
|
115
|
+
@click.pass_context
|
|
116
|
+
def stop_cmd(ctx: click.Context, strategy_id: str):
|
|
117
|
+
"""Stop a live on-chain bot."""
|
|
118
|
+
client: FastBooksClient = ctx.obj
|
|
119
|
+
try:
|
|
120
|
+
emit(ctx, client.onchain_live_stop(strategy_id))
|
|
121
|
+
except Exception as e: # noqa: BLE001
|
|
122
|
+
emit_error(ctx, str(e))
|
|
123
|
+
ctx.exit(1)
|