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,294 @@
|
|
|
1
|
+
"""Config command group — service URLs, defaults, diagnostics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from ..client import FastBooksClient, DEFAULT_ROUTING_URL, DEFAULT_BOTS_URL
|
|
10
|
+
from ..output import emit, emit_error
|
|
11
|
+
from .. import config_store
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
def config():
|
|
16
|
+
"""Configuration and diagnostics.
|
|
17
|
+
|
|
18
|
+
Environment variables:
|
|
19
|
+
FASTBOOKS_ROUTING_URL — Routing/API service (default: http://localhost:3002)
|
|
20
|
+
FASTBOOKS_BOTS_URL — Bots service (default: http://localhost:8003)
|
|
21
|
+
ALPACA_API_KEY — Alpaca API key
|
|
22
|
+
ALPACA_API_SECRET — Alpaca API secret
|
|
23
|
+
EODHD_API_TOKEN — EODHD market data token
|
|
24
|
+
IBKR_ENCRYPTION_KEY — AES-256 encryption key for IBKR credentials
|
|
25
|
+
SOLANA_RPC_URL — Solana RPC endpoint
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@config.command()
|
|
31
|
+
@click.pass_context
|
|
32
|
+
def show(ctx):
|
|
33
|
+
"""Show current configuration and service URLs.
|
|
34
|
+
|
|
35
|
+
Example: fastbooks config show
|
|
36
|
+
"""
|
|
37
|
+
client: FastBooksClient = ctx.obj
|
|
38
|
+
data = {
|
|
39
|
+
"routing_url": client.routing_url,
|
|
40
|
+
"bots_url": client.bots_url,
|
|
41
|
+
"timeout": client.timeout,
|
|
42
|
+
"env": {
|
|
43
|
+
"FASTBOOKS_ROUTING_URL": os.environ.get("FASTBOOKS_ROUTING_URL", f"(default: {DEFAULT_ROUTING_URL})"),
|
|
44
|
+
"FASTBOOKS_BOTS_URL": os.environ.get("FASTBOOKS_BOTS_URL", f"(default: {DEFAULT_BOTS_URL})"),
|
|
45
|
+
"ALPACA_API_KEY": "***" if os.environ.get("ALPACA_API_KEY") else "(not set)",
|
|
46
|
+
"ALPACA_API_SECRET": "***" if os.environ.get("ALPACA_API_SECRET") else "(not set)",
|
|
47
|
+
"EODHD_API_TOKEN": "***" if os.environ.get("EODHD_API_TOKEN") else "(not set)",
|
|
48
|
+
"IBKR_ENCRYPTION_KEY": "***" if os.environ.get("IBKR_ENCRYPTION_KEY") else "(not set)",
|
|
49
|
+
"SOLANA_RPC_URL": os.environ.get("SOLANA_RPC_URL", "(not set)"),
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
data["stored"] = config_store.redacted()
|
|
53
|
+
emit(ctx, data, lambda d: _format_config(d))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@config.command("set-key")
|
|
57
|
+
@click.argument("exchange")
|
|
58
|
+
@click.option("--api-key", "api_key", default=None, help="Exchange API key.")
|
|
59
|
+
@click.option("--secret", default=None, help="Exchange API secret / wallet key.")
|
|
60
|
+
@click.option("--password", default=None, help="Passphrase (okx/kucoin/etc.), if required.")
|
|
61
|
+
@click.pass_context
|
|
62
|
+
def set_key(ctx, exchange, api_key, secret, password):
|
|
63
|
+
"""Store trading keys for a ccxt exchange in the local config (agent-friendly, non-interactive).
|
|
64
|
+
|
|
65
|
+
e.g. fastbooks config set-key binance --api-key AK --secret SK
|
|
66
|
+
"""
|
|
67
|
+
if not any([api_key, secret, password]):
|
|
68
|
+
emit_error(ctx, "Provide at least one of --api-key / --secret / --password.")
|
|
69
|
+
ctx.exit(1)
|
|
70
|
+
config_store.set_credentials(exchange, api_key, secret, password)
|
|
71
|
+
config_store.activate(exchange)
|
|
72
|
+
emit(ctx, {"exchange": exchange.lower(), "stored": True, "activated": True},
|
|
73
|
+
lambda d: f" Stored keys for {d['exchange']} and activated it.")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@config.command("activate")
|
|
77
|
+
@click.argument("exchange")
|
|
78
|
+
@click.pass_context
|
|
79
|
+
def activate_cmd(ctx, exchange):
|
|
80
|
+
"""Activate an exchange (data + trading if keys are set)."""
|
|
81
|
+
config_store.activate(exchange)
|
|
82
|
+
emit(ctx, {"activated": config_store.activated()}, lambda d: f" Activated. Now: {', '.join(d['activated'])}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@config.command("deactivate")
|
|
86
|
+
@click.argument("exchange")
|
|
87
|
+
@click.pass_context
|
|
88
|
+
def deactivate_cmd(ctx, exchange):
|
|
89
|
+
"""Deactivate an exchange."""
|
|
90
|
+
config_store.deactivate(exchange)
|
|
91
|
+
emit(ctx, {"activated": config_store.activated()}, lambda d: f" Deactivated. Now: {', '.join(d['activated']) or '(none)'}")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _format_config(data: dict) -> str:
|
|
95
|
+
lines = [
|
|
96
|
+
"",
|
|
97
|
+
" FastBooks CLI Configuration",
|
|
98
|
+
" ===========================",
|
|
99
|
+
f" Routing URL: {data['routing_url']}",
|
|
100
|
+
f" Bots URL: {data['bots_url']}",
|
|
101
|
+
f" Timeout: {data['timeout']}s",
|
|
102
|
+
"",
|
|
103
|
+
" Environment Variables:",
|
|
104
|
+
]
|
|
105
|
+
for key, val in data.get("env", {}).items():
|
|
106
|
+
lines.append(f" {key}: {val}")
|
|
107
|
+
return "\n".join(lines)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@config.command()
|
|
111
|
+
@click.pass_context
|
|
112
|
+
def health(ctx):
|
|
113
|
+
"""Check connectivity to FastBooks services.
|
|
114
|
+
|
|
115
|
+
Tests connection to the routing API and bots service.
|
|
116
|
+
|
|
117
|
+
Example: fastbooks config health
|
|
118
|
+
"""
|
|
119
|
+
client: FastBooksClient = ctx.obj
|
|
120
|
+
results = {}
|
|
121
|
+
|
|
122
|
+
# Test routing service
|
|
123
|
+
try:
|
|
124
|
+
client._get(client.routing_url, "/api/execution/venues")
|
|
125
|
+
results["routing"] = {"status": "ok", "url": client.routing_url}
|
|
126
|
+
except Exception as e:
|
|
127
|
+
results["routing"] = {"status": "error", "url": client.routing_url, "error": str(e)}
|
|
128
|
+
|
|
129
|
+
# Test bots service
|
|
130
|
+
try:
|
|
131
|
+
client._get(client.bots_url, "/bots")
|
|
132
|
+
results["bots"] = {"status": "ok", "url": client.bots_url}
|
|
133
|
+
except Exception as e:
|
|
134
|
+
results["bots"] = {"status": "error", "url": client.bots_url, "error": str(e)}
|
|
135
|
+
|
|
136
|
+
emit(ctx, results, lambda d: _format_health(d))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _format_health(data: dict) -> str:
|
|
140
|
+
lines = ["", " Service Health", " =============="]
|
|
141
|
+
for service, info in data.items():
|
|
142
|
+
status = info.get("status", "unknown")
|
|
143
|
+
color = "green" if status == "ok" else "red"
|
|
144
|
+
symbol = "+" if status == "ok" else "x"
|
|
145
|
+
line = f" [{symbol}] {service}: {info.get('url', '?')}"
|
|
146
|
+
if info.get("error"):
|
|
147
|
+
line += f" — {info['error']}"
|
|
148
|
+
lines.append(click.style(line, fg=color))
|
|
149
|
+
return "\n".join(lines)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@config.command()
|
|
153
|
+
@click.pass_context
|
|
154
|
+
def exchanges(ctx):
|
|
155
|
+
"""List all supported exchanges with their capabilities and requirements.
|
|
156
|
+
|
|
157
|
+
Example: fastbooks config exchanges
|
|
158
|
+
"""
|
|
159
|
+
data = {
|
|
160
|
+
"exchanges": [
|
|
161
|
+
{
|
|
162
|
+
"name": "binance",
|
|
163
|
+
"type": "CEX",
|
|
164
|
+
"markets": ["spot", "perpetual"],
|
|
165
|
+
"auth": "API key + secret",
|
|
166
|
+
"features": ["orders", "balance", "positions", "leverage"],
|
|
167
|
+
"notes": "Testnet available. Most liquid crypto exchange.",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"name": "bybit",
|
|
171
|
+
"type": "CEX",
|
|
172
|
+
"markets": ["spot", "perpetual"],
|
|
173
|
+
"auth": "API key + secret",
|
|
174
|
+
"features": ["orders", "balance", "positions", "leverage"],
|
|
175
|
+
"notes": "Unified trading account.",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "okx",
|
|
179
|
+
"type": "CEX",
|
|
180
|
+
"markets": ["spot", "perpetual"],
|
|
181
|
+
"auth": "API key + secret + passphrase",
|
|
182
|
+
"features": ["orders", "balance", "positions", "leverage"],
|
|
183
|
+
"notes": "Passphrase required.",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "kraken",
|
|
187
|
+
"type": "CEX",
|
|
188
|
+
"markets": ["spot", "perpetual"],
|
|
189
|
+
"auth": "API key + secret",
|
|
190
|
+
"features": ["orders", "balance", "positions"],
|
|
191
|
+
"notes": "Pro API for perps.",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"name": "coinbase",
|
|
195
|
+
"type": "CEX",
|
|
196
|
+
"markets": ["spot"],
|
|
197
|
+
"auth": "API key + secret",
|
|
198
|
+
"features": ["orders", "balance"],
|
|
199
|
+
"notes": "Advanced Trade API. Spot only.",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"name": "kucoin",
|
|
203
|
+
"type": "CEX",
|
|
204
|
+
"markets": ["spot"],
|
|
205
|
+
"auth": "API key + secret + passphrase",
|
|
206
|
+
"features": ["orders", "balance"],
|
|
207
|
+
"notes": "Passphrase required. Spot only.",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"name": "hyperliquid",
|
|
211
|
+
"type": "DEX (on-chain perps)",
|
|
212
|
+
"markets": ["perpetual"],
|
|
213
|
+
"auth": "EVM private key (64 hex chars)",
|
|
214
|
+
"features": ["orders", "positions", "leverage"],
|
|
215
|
+
"notes": "On-chain perpetuals on Arbitrum. No API secret needed, uses wallet signing.",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"name": "ibkr",
|
|
219
|
+
"type": "Traditional broker",
|
|
220
|
+
"markets": ["stock", "options", "forex", "futures"],
|
|
221
|
+
"auth": "Gateway connection + encrypted credentials",
|
|
222
|
+
"features": ["orders", "balance", "positions", "order_history"],
|
|
223
|
+
"notes": "Requires IB Gateway running (port 4001/4002). Paper/live modes.",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"name": "alpaca",
|
|
227
|
+
"type": "Traditional broker",
|
|
228
|
+
"markets": ["stock", "crypto"],
|
|
229
|
+
"auth": "API key + secret",
|
|
230
|
+
"features": ["orders", "balance"],
|
|
231
|
+
"notes": "Paper mode via sandbox flag. Commission-free stocks.",
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"name": "deribit",
|
|
235
|
+
"type": "CEX (options)",
|
|
236
|
+
"markets": ["options", "perpetual"],
|
|
237
|
+
"auth": "API key + secret",
|
|
238
|
+
"features": ["orderbooks", "options_chain"],
|
|
239
|
+
"notes": "Options and perps. Testnet available.",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"name": "polymarket",
|
|
243
|
+
"type": "Prediction market (Polygon)",
|
|
244
|
+
"markets": ["prediction"],
|
|
245
|
+
"auth": "EVM private key (POLYMARKET_PRIVATE_KEY) for trading; read-only without",
|
|
246
|
+
"features": [
|
|
247
|
+
"markets", "events", "search", "tags",
|
|
248
|
+
"orderbooks", "prices", "price-history",
|
|
249
|
+
"limit-orders", "market-orders", "cancel",
|
|
250
|
+
"positions", "balance", "trades",
|
|
251
|
+
"ctf-split", "ctf-merge", "ctf-redeem",
|
|
252
|
+
"bridge", "approvals", "leaderboard",
|
|
253
|
+
],
|
|
254
|
+
"notes": "Full Polymarket integration: browse, trade, CTF ops, bridging. Use 'fastbooks polymarket' subcommands.",
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"name": "limitless",
|
|
258
|
+
"type": "Prediction market",
|
|
259
|
+
"markets": ["prediction"],
|
|
260
|
+
"auth": "None (read-only)",
|
|
261
|
+
"features": ["orderbooks"],
|
|
262
|
+
"notes": "Alternative prediction market.",
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"name": "jupiter",
|
|
266
|
+
"type": "DEX aggregator (Solana)",
|
|
267
|
+
"markets": ["spot"],
|
|
268
|
+
"auth": "Solana wallet",
|
|
269
|
+
"features": ["tickers", "swap"],
|
|
270
|
+
"notes": "5000+ Solana tokens. Best price routing.",
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"name": "uniswap",
|
|
274
|
+
"type": "DEX (Ethereum)",
|
|
275
|
+
"markets": ["spot"],
|
|
276
|
+
"auth": "EVM wallet",
|
|
277
|
+
"features": ["tickers", "pools"],
|
|
278
|
+
"notes": "Uniswap V3. Ethereum mainnet.",
|
|
279
|
+
},
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
emit(ctx, data, lambda d: _format_exchanges(d))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _format_exchanges(data: dict) -> str:
|
|
286
|
+
lines = ["", " Supported Exchanges", " ===================", ""]
|
|
287
|
+
for ex in data["exchanges"]:
|
|
288
|
+
lines.append(f" {ex['name'].upper()} ({ex['type']})")
|
|
289
|
+
lines.append(f" Markets: {', '.join(ex['markets'])}")
|
|
290
|
+
lines.append(f" Auth: {ex['auth']}")
|
|
291
|
+
lines.append(f" Features: {', '.join(ex['features'])}")
|
|
292
|
+
lines.append(f" Notes: {ex['notes']}")
|
|
293
|
+
lines.append("")
|
|
294
|
+
return "\n".join(lines)
|