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,298 @@
|
|
|
1
|
+
"""Execution command group — orders, balance, positions, leverage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..client import FastBooksClient
|
|
8
|
+
from ..output import emit, emit_error, format_table
|
|
9
|
+
|
|
10
|
+
VENUES = [
|
|
11
|
+
"binance", "bybit", "okx", "kraken", "coinbase", "kucoin",
|
|
12
|
+
"hyperliquid", "ibkr", "alpaca", "paper", "universal", "gmx",
|
|
13
|
+
"oanda", "oanda_paper", "saxo", "saxo_paper",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
ORDER_TYPES = ["market", "limit", "stop", "stop_limit", "trailing_stop"]
|
|
17
|
+
SIDES = ["buy", "sell"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.group()
|
|
21
|
+
def execute():
|
|
22
|
+
"""Order execution: place, cancel, list orders, balance, positions.
|
|
23
|
+
|
|
24
|
+
Supported venues: binance, bybit, okx, kraken, coinbase, kucoin,
|
|
25
|
+
hyperliquid, ibkr, alpaca, universal (internal OB).
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ── venues ───────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
@execute.command()
|
|
33
|
+
@click.pass_context
|
|
34
|
+
def venues(ctx):
|
|
35
|
+
"""List all available venues and their capabilities.
|
|
36
|
+
|
|
37
|
+
Shows which operations each venue supports (orders, balance, positions).
|
|
38
|
+
"""
|
|
39
|
+
client: FastBooksClient = ctx.obj
|
|
40
|
+
try:
|
|
41
|
+
data = client.venues()
|
|
42
|
+
emit(ctx, data, lambda d: _format_venues(d))
|
|
43
|
+
except Exception as e:
|
|
44
|
+
emit_error(ctx, str(e))
|
|
45
|
+
ctx.exit(1)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _format_venues(data: dict) -> str:
|
|
49
|
+
items = data if isinstance(data, list) else data.get("venues", data.get("data", []))
|
|
50
|
+
if not isinstance(items, list):
|
|
51
|
+
return str(data)
|
|
52
|
+
return format_table(
|
|
53
|
+
items,
|
|
54
|
+
["venue", "placeOrder", "cancelOrder", "getBalance", "getPositions"],
|
|
55
|
+
["Venue", "Place", "Cancel", "Balance", "Positions"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ── place order ──────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
@execute.command()
|
|
62
|
+
@click.argument("venue", type=click.Choice(VENUES, case_sensitive=False))
|
|
63
|
+
@click.argument("symbol")
|
|
64
|
+
@click.argument("side", type=click.Choice(SIDES, case_sensitive=False))
|
|
65
|
+
@click.argument("order_type", type=click.Choice(ORDER_TYPES, case_sensitive=False))
|
|
66
|
+
@click.argument("amount", type=float)
|
|
67
|
+
@click.option("--price", "-p", type=float, default=None, help="Limit price (required for limit orders).")
|
|
68
|
+
@click.option("--client-id", type=str, default=None, help="Client order ID for tracking.")
|
|
69
|
+
@click.option("--confirm/--no-confirm", default=True, help="Require confirmation before placing.")
|
|
70
|
+
@click.pass_context
|
|
71
|
+
def order(ctx, venue, symbol, side, order_type, amount, price, client_id, confirm):
|
|
72
|
+
"""Place an order on a venue.
|
|
73
|
+
|
|
74
|
+
VENUE: Exchange or broker to execute on.
|
|
75
|
+
SYMBOL: Trading pair (e.g. BTC/USDT, AAPL, ETH/USDC).
|
|
76
|
+
SIDE: buy or sell.
|
|
77
|
+
ORDER_TYPE: market, limit, stop, stop_limit, trailing_stop.
|
|
78
|
+
AMOUNT: Quantity to trade.
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
fastbooks execute order binance BTC/USDT buy market 0.01
|
|
82
|
+
fastbooks execute order alpaca AAPL buy limit 10 --price 150.00
|
|
83
|
+
fastbooks execute order hyperliquid ETH/USDC sell market 1.5
|
|
84
|
+
fastbooks execute order ibkr MSFT buy market 100
|
|
85
|
+
"""
|
|
86
|
+
client: FastBooksClient = ctx.obj
|
|
87
|
+
|
|
88
|
+
if order_type == "limit" and price is None:
|
|
89
|
+
emit_error(ctx, "Limit orders require --price")
|
|
90
|
+
ctx.exit(1)
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if confirm and not ctx.find_root().params.get("json_output", False):
|
|
94
|
+
click.echo(f"\n Order: {side.upper()} {amount} {symbol} @ {order_type} on {venue}")
|
|
95
|
+
if price:
|
|
96
|
+
click.echo(f" Price: {price}")
|
|
97
|
+
if not click.confirm("\n Confirm?"):
|
|
98
|
+
click.echo(" Cancelled.")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
data = client.place_order(venue, symbol, side, order_type, amount, price, client_id)
|
|
103
|
+
emit(ctx, data, lambda d: f" Order placed: {d}")
|
|
104
|
+
except Exception as e:
|
|
105
|
+
emit_error(ctx, str(e))
|
|
106
|
+
ctx.exit(1)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ── cancel order ─────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
@execute.command()
|
|
112
|
+
@click.argument("venue", type=click.Choice(VENUES, case_sensitive=False))
|
|
113
|
+
@click.argument("order_id")
|
|
114
|
+
@click.option("--symbol", "-s", type=str, default=None, help="Symbol (required for some venues).")
|
|
115
|
+
@click.pass_context
|
|
116
|
+
def cancel(ctx, venue, order_id, symbol):
|
|
117
|
+
"""Cancel an open order.
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
fastbooks execute cancel binance abc123 --symbol BTC/USDT
|
|
121
|
+
fastbooks execute cancel hyperliquid def456
|
|
122
|
+
"""
|
|
123
|
+
client: FastBooksClient = ctx.obj
|
|
124
|
+
try:
|
|
125
|
+
data = client.cancel_order(venue, order_id, symbol)
|
|
126
|
+
emit(ctx, data, lambda d: f" Order {order_id} cancelled.")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
emit_error(ctx, str(e))
|
|
129
|
+
ctx.exit(1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ── list orders ──────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
@execute.command("orders")
|
|
135
|
+
@click.argument("venue", type=click.Choice(VENUES, case_sensitive=False))
|
|
136
|
+
@click.option("--symbol", "-s", type=str, default=None, help="Filter by symbol.")
|
|
137
|
+
@click.pass_context
|
|
138
|
+
def list_orders(ctx, venue, symbol):
|
|
139
|
+
"""List open orders on a venue.
|
|
140
|
+
|
|
141
|
+
Examples:
|
|
142
|
+
fastbooks execute orders binance
|
|
143
|
+
fastbooks execute orders ibkr --symbol AAPL
|
|
144
|
+
"""
|
|
145
|
+
client: FastBooksClient = ctx.obj
|
|
146
|
+
try:
|
|
147
|
+
data = client.list_orders(venue, symbol)
|
|
148
|
+
emit(ctx, data, lambda d: _format_orders(d))
|
|
149
|
+
except Exception as e:
|
|
150
|
+
emit_error(ctx, str(e))
|
|
151
|
+
ctx.exit(1)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _format_orders(data) -> str:
|
|
155
|
+
items = data if isinstance(data, list) else data.get("orders", data.get("data", []))
|
|
156
|
+
if not isinstance(items, list):
|
|
157
|
+
return str(data)
|
|
158
|
+
if not items:
|
|
159
|
+
return " No open orders."
|
|
160
|
+
return format_table(
|
|
161
|
+
items,
|
|
162
|
+
["orderId", "symbol", "side", "type", "amount", "price", "status"],
|
|
163
|
+
["Order ID", "Symbol", "Side", "Type", "Amount", "Price", "Status"],
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ── get order ────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
@execute.command("order-status")
|
|
170
|
+
@click.argument("venue", type=click.Choice(VENUES, case_sensitive=False))
|
|
171
|
+
@click.argument("order_id")
|
|
172
|
+
@click.option("--symbol", "-s", type=str, default=None)
|
|
173
|
+
@click.pass_context
|
|
174
|
+
def get_order(ctx, venue, order_id, symbol):
|
|
175
|
+
"""Get status of a specific order.
|
|
176
|
+
|
|
177
|
+
Examples:
|
|
178
|
+
fastbooks execute order-status binance abc123
|
|
179
|
+
"""
|
|
180
|
+
client: FastBooksClient = ctx.obj
|
|
181
|
+
try:
|
|
182
|
+
data = client.get_order(venue, order_id, symbol)
|
|
183
|
+
emit(ctx, data)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
emit_error(ctx, str(e))
|
|
186
|
+
ctx.exit(1)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# ── balance ──────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
@execute.command()
|
|
192
|
+
@click.argument("venue", type=click.Choice(VENUES, case_sensitive=False))
|
|
193
|
+
@click.option("--asset", "-a", type=str, default=None, help="Filter by asset (e.g. BTC, USD).")
|
|
194
|
+
@click.pass_context
|
|
195
|
+
def balance(ctx, venue, asset):
|
|
196
|
+
"""Get account balance / holdings.
|
|
197
|
+
|
|
198
|
+
Examples:
|
|
199
|
+
fastbooks execute balance binance
|
|
200
|
+
fastbooks execute balance ibkr --asset USD
|
|
201
|
+
fastbooks execute balance alpaca
|
|
202
|
+
"""
|
|
203
|
+
client: FastBooksClient = ctx.obj
|
|
204
|
+
try:
|
|
205
|
+
data = client.balance(venue, asset)
|
|
206
|
+
emit(ctx, data, lambda d: _format_balance(d))
|
|
207
|
+
except Exception as e:
|
|
208
|
+
emit_error(ctx, str(e))
|
|
209
|
+
ctx.exit(1)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _format_balance(data) -> str:
|
|
213
|
+
items = data if isinstance(data, list) else data.get("balances", data.get("data", []))
|
|
214
|
+
if not isinstance(items, list):
|
|
215
|
+
return str(data)
|
|
216
|
+
return format_table(
|
|
217
|
+
items,
|
|
218
|
+
["asset", "free", "locked", "total"],
|
|
219
|
+
["Asset", "Free", "Locked", "Total"],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ── positions ────────────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
@execute.command()
|
|
226
|
+
@click.argument("venue", type=click.Choice(VENUES, case_sensitive=False))
|
|
227
|
+
@click.pass_context
|
|
228
|
+
def positions(ctx, venue):
|
|
229
|
+
"""Get open positions (perpetuals / margin).
|
|
230
|
+
|
|
231
|
+
Examples:
|
|
232
|
+
fastbooks execute positions hyperliquid
|
|
233
|
+
fastbooks execute positions binance
|
|
234
|
+
"""
|
|
235
|
+
client: FastBooksClient = ctx.obj
|
|
236
|
+
try:
|
|
237
|
+
data = client.positions(venue)
|
|
238
|
+
emit(ctx, data, lambda d: _format_positions(d))
|
|
239
|
+
except Exception as e:
|
|
240
|
+
emit_error(ctx, str(e))
|
|
241
|
+
ctx.exit(1)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _format_positions(data) -> str:
|
|
245
|
+
items = data if isinstance(data, list) else data.get("positions", data.get("data", []))
|
|
246
|
+
if not isinstance(items, list):
|
|
247
|
+
return str(data)
|
|
248
|
+
if not items:
|
|
249
|
+
return " No open positions."
|
|
250
|
+
return format_table(
|
|
251
|
+
items,
|
|
252
|
+
["symbol", "side", "size", "entryPrice", "unrealizedPnl", "leverage"],
|
|
253
|
+
["Symbol", "Side", "Size", "Entry", "Unrealized PnL", "Leverage"],
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ── leverage ─────────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
@execute.command()
|
|
260
|
+
@click.argument("venue", type=click.Choice(VENUES, case_sensitive=False))
|
|
261
|
+
@click.argument("symbol")
|
|
262
|
+
@click.argument("leverage_value", type=int)
|
|
263
|
+
@click.option("--cross/--isolated", default=False, help="Cross or isolated margin mode.")
|
|
264
|
+
@click.pass_context
|
|
265
|
+
def leverage(ctx, venue, symbol, leverage_value, cross):
|
|
266
|
+
"""Update leverage for a perpetual position.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
fastbooks execute leverage hyperliquid ETH/USDC 10
|
|
270
|
+
fastbooks execute leverage binance BTC/USDT 5 --cross
|
|
271
|
+
"""
|
|
272
|
+
client: FastBooksClient = ctx.obj
|
|
273
|
+
try:
|
|
274
|
+
data = client.update_leverage(venue, symbol, leverage_value, cross)
|
|
275
|
+
emit(ctx, data, lambda d: f" Leverage set to {leverage_value}x for {symbol} on {venue}")
|
|
276
|
+
except Exception as e:
|
|
277
|
+
emit_error(ctx, str(e))
|
|
278
|
+
ctx.exit(1)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# ── order history ────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
@execute.command()
|
|
284
|
+
@click.option("--venue", "-v", type=click.Choice(VENUES, case_sensitive=False), default="ibkr")
|
|
285
|
+
@click.pass_context
|
|
286
|
+
def history(ctx, venue):
|
|
287
|
+
"""Get order history (filled, cancelled, rejected).
|
|
288
|
+
|
|
289
|
+
Examples:
|
|
290
|
+
fastbooks execute history --venue ibkr
|
|
291
|
+
"""
|
|
292
|
+
client: FastBooksClient = ctx.obj
|
|
293
|
+
try:
|
|
294
|
+
data = client.order_history(venue)
|
|
295
|
+
emit(ctx, data, lambda d: _format_orders(d))
|
|
296
|
+
except Exception as e:
|
|
297
|
+
emit_error(ctx, str(e))
|
|
298
|
+
ctx.exit(1)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""`init` — one-command onboarding for controlling FastBooks with an AI agent.
|
|
2
|
+
|
|
3
|
+
Sets up, in ~/.fastbooks/config.json (0600): the FastBooks data key (so the bot is
|
|
4
|
+
throttled), which exchanges are activated, per-exchange trading keys, and an MCP host
|
|
5
|
+
config. Interactive by default; fully non-interactive for agents/scripts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from ..client import FastBooksClient
|
|
15
|
+
from ..output import emit, emit_error
|
|
16
|
+
from .. import config_store
|
|
17
|
+
|
|
18
|
+
# Curated activation catalog: (title, exchange ids, backend kind)
|
|
19
|
+
CATALOG = [
|
|
20
|
+
("Crypto CEX — spot + perps (ccxt: apiKey + secret [+ password])",
|
|
21
|
+
["binance", "bybit", "okx", "kraken", "coinbase", "kucoin", "bitget", "gate", "mexc", "htx"], "ccxt"),
|
|
22
|
+
("On-chain perps / DEX — via ccxt (hyperliquid/dydx/… use a wallet key as 'secret')",
|
|
23
|
+
["hyperliquid", "dydx", "paradex", "apex", "woofipro"], "ccxt"),
|
|
24
|
+
("Stocks — FastBooks platform (connect: `fastbooks --token <JWT> wallet add ...`)",
|
|
25
|
+
["alpaca", "ibkr"], "platform"),
|
|
26
|
+
("Forex — FastBooks platform",
|
|
27
|
+
["oanda", "saxo"], "platform"),
|
|
28
|
+
("Prediction markets — FastBooks platform (Polymarket tradable via `execute`)",
|
|
29
|
+
["polymarket", "limitless"], "platform"),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _kind_of(ex: str) -> str:
|
|
34
|
+
for _title, ids, kind in CATALOG:
|
|
35
|
+
if ex in ids:
|
|
36
|
+
return kind
|
|
37
|
+
return "ccxt" # default: treat unknown as a ccxt exchange id
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@click.command("init")
|
|
41
|
+
@click.option("--activate", "activate_csv", default=None, help="Comma list of exchanges to activate (non-interactive).")
|
|
42
|
+
@click.option("--mint-key", is_flag=True, help="Mint a free FastBooks data key (x-api-key throttle).")
|
|
43
|
+
@click.option("--api-key", "api_key", default=None, help="Store this existing FastBooks data key instead of minting.")
|
|
44
|
+
@click.option("--mcp-config/--no-mcp-config", default=True, help="Write an MCP host config for agents (~/.fastbooks/mcp.json).")
|
|
45
|
+
@click.option("--yes", "-y", is_flag=True, help="Non-interactive: apply flags with no prompts.")
|
|
46
|
+
@click.pass_context
|
|
47
|
+
def init_cmd(ctx, activate_csv, mint_key, api_key, mcp_config, yes):
|
|
48
|
+
"""Set up FastBooks for your AI agent — data key, exchange activation, MCP config.
|
|
49
|
+
|
|
50
|
+
Interactive: fastbooks init
|
|
51
|
+
Agent/scripted: fastbooks --json init -y --mint-key --activate binance,hyperliquid,polymarket
|
|
52
|
+
"""
|
|
53
|
+
client: FastBooksClient = ctx.obj
|
|
54
|
+
json_mode = bool(ctx.find_root().params.get("json_output"))
|
|
55
|
+
interactive = not (yes or json_mode or activate_csv or mint_key or api_key)
|
|
56
|
+
|
|
57
|
+
summary = {
|
|
58
|
+
"config_path": str(config_store.CONFIG_PATH),
|
|
59
|
+
"data_key": None,
|
|
60
|
+
"activated": [],
|
|
61
|
+
"credentials_set": [],
|
|
62
|
+
"mcp_config": None,
|
|
63
|
+
"next_steps": [],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ── Step 1: FastBooks data key (per-bot throttle) ──
|
|
67
|
+
key = api_key
|
|
68
|
+
if not key and (mint_key or interactive and click.confirm(
|
|
69
|
+
"Mint a free FastBooks data key now (lets the platform rate-throttle your bot)?", default=True)):
|
|
70
|
+
try:
|
|
71
|
+
res = client.create_fastbooks_api_key("cli-agent")
|
|
72
|
+
key = res.get("apiKey") or res.get("api_key")
|
|
73
|
+
if interactive and key:
|
|
74
|
+
click.echo(f" minted {key}")
|
|
75
|
+
except Exception as e: # noqa: BLE001
|
|
76
|
+
if interactive:
|
|
77
|
+
click.echo(f" (could not mint — {e}; is the backend up?)")
|
|
78
|
+
if interactive and not key:
|
|
79
|
+
entered = click.prompt(" Or paste an existing fb_live_ key (blank to skip)", default="", show_default=False)
|
|
80
|
+
key = entered.strip() or None
|
|
81
|
+
if key:
|
|
82
|
+
config_store.set_api_key(key)
|
|
83
|
+
summary["data_key"] = "set"
|
|
84
|
+
|
|
85
|
+
# ── Step 2: choose exchanges to activate ──
|
|
86
|
+
chosen: list[str] = []
|
|
87
|
+
if activate_csv:
|
|
88
|
+
chosen = [x.strip().lower() for x in activate_csv.split(",") if x.strip()]
|
|
89
|
+
elif interactive:
|
|
90
|
+
click.echo("\n Available venues:")
|
|
91
|
+
for title, ids, _kind in CATALOG:
|
|
92
|
+
click.echo(f" • {title}\n {', '.join(ids)}")
|
|
93
|
+
raw = click.prompt("\n Activate which? (comma list, 'all-cex', or blank)", default="", show_default=False)
|
|
94
|
+
if raw.strip().lower() == "all-cex":
|
|
95
|
+
chosen = list(CATALOG[0][1])
|
|
96
|
+
else:
|
|
97
|
+
chosen = [x.strip().lower() for x in raw.split(",") if x.strip()]
|
|
98
|
+
|
|
99
|
+
for ex in chosen:
|
|
100
|
+
config_store.activate(ex)
|
|
101
|
+
summary["activated"].append(ex)
|
|
102
|
+
|
|
103
|
+
# ── Step 3: trading keys for activated ccxt venues ──
|
|
104
|
+
for ex in chosen:
|
|
105
|
+
kind = _kind_of(ex)
|
|
106
|
+
if kind == "platform":
|
|
107
|
+
summary["next_steps"].append(
|
|
108
|
+
f"Connect {ex} (platform venue): fastbooks --token <JWT> wallet add {ex} --api-key ... --secret ..."
|
|
109
|
+
)
|
|
110
|
+
continue
|
|
111
|
+
if interactive and click.confirm(f" Add trading keys for '{ex}' now? (skip = data-only)", default=False):
|
|
112
|
+
ak = click.prompt(f" {ex} apiKey", default="", show_default=False).strip()
|
|
113
|
+
sk = click.prompt(f" {ex} secret/wallet-key", default="", show_default=False, hide_input=True).strip()
|
|
114
|
+
pw = click.prompt(f" {ex} password/passphrase (blank if none)", default="", show_default=False, hide_input=True).strip()
|
|
115
|
+
config_store.set_credentials(ex, ak or None, sk or None, pw or None)
|
|
116
|
+
summary["credentials_set"].append(ex)
|
|
117
|
+
|
|
118
|
+
# ── Step 4: MCP host config ──
|
|
119
|
+
if mcp_config:
|
|
120
|
+
env = {"FASTBOOKS_API_KEY": key} if key else {}
|
|
121
|
+
host_cfg = {"mcpServers": {"fastbooks": {"command": "fastbooks-mcp", **({"env": env} if env else {})}}}
|
|
122
|
+
config_store.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
path = config_store.CONFIG_DIR / "mcp.json"
|
|
124
|
+
path.write_text(json.dumps(host_cfg, indent=2))
|
|
125
|
+
summary["mcp_config"] = str(path)
|
|
126
|
+
|
|
127
|
+
summary["next_steps"] += [
|
|
128
|
+
"Data (keyless): fastbooks --json direct ticker hyperliquid BTC/USDC:USDC",
|
|
129
|
+
"Trade (activated): fastbooks direct order binance BTC/USDT buy 0.001 --type limit --price 60000",
|
|
130
|
+
"Agent (MCP): add ~/.fastbooks/mcp.json to your MCP host — or run `fastbooks-mcp`",
|
|
131
|
+
]
|
|
132
|
+
emit(ctx, summary, _format)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _format(s: dict) -> str:
|
|
136
|
+
lines = [
|
|
137
|
+
"",
|
|
138
|
+
" FastBooks onboarding complete",
|
|
139
|
+
" =============================",
|
|
140
|
+
f" Config: {s['config_path']}",
|
|
141
|
+
f" Data key: {s['data_key'] or '(none — mint later with `fastbooks apikey create`)'}",
|
|
142
|
+
f" Activated: {', '.join(s['activated']) or '(none)'}",
|
|
143
|
+
f" Keys set: {', '.join(s['credentials_set']) or '(none — data-only)'}",
|
|
144
|
+
f" MCP config: {s['mcp_config'] or '(skipped)'}",
|
|
145
|
+
"",
|
|
146
|
+
" Next:",
|
|
147
|
+
]
|
|
148
|
+
lines += [f" • {n}" for n in s["next_steps"]]
|
|
149
|
+
return "\n".join(lines)
|