@farazirfan/costar-server-executor 1.7.28 → 1.7.30
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/dist/agent/pi-embedded-runner/run.d.ts.map +1 -1
- package/dist/agent/pi-embedded-runner/run.js +21 -0
- package/dist/agent/pi-embedded-runner/run.js.map +1 -1
- package/dist/agent/pi-embedded-runner/subscribe.d.ts +4 -0
- package/dist/agent/pi-embedded-runner/subscribe.d.ts.map +1 -1
- package/dist/agent/pi-embedded-runner/subscribe.js +34 -2
- package/dist/agent/pi-embedded-runner/subscribe.js.map +1 -1
- package/package.json +1 -1
- package/skills/okx/LEARNING.md +41 -0
- package/skills/okx/SKILL.md +231 -0
- package/skills/okx/references/api-reference.md +201 -0
- package/skills/okx/references/order-types.md +251 -0
- package/skills/okx/references/products.md +128 -0
- package/skills/okx/scripts/okx-trade.ts +273 -0
- package/skills/trading/SKILL.md +89 -7
- package/skills/trading/scripts/dashboard-server.py +191 -0
- package/skills/trading/scripts/data-api.py +211 -0
- package/skills/trading/scripts/trade-dashboard-template.html +81 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* OKX Trade Helper — CLI tool for common OKX operations.
|
|
4
|
+
* Agent can use this directly or create new scripts based on this pattern.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx tsx scripts/okx-trade.ts balances
|
|
8
|
+
* npx tsx scripts/okx-trade.ts positions
|
|
9
|
+
* npx tsx scripts/okx-trade.ts price BTC-USDT
|
|
10
|
+
* npx tsx scripts/okx-trade.ts market-buy BTC-USDT 100
|
|
11
|
+
* npx tsx scripts/okx-trade.ts market-sell BTC-USDT 0.001
|
|
12
|
+
* npx tsx scripts/okx-trade.ts limit-buy BTC-USDT 0.001 95000
|
|
13
|
+
* npx tsx scripts/okx-trade.ts limit-sell BTC-USDT 0.001 105000
|
|
14
|
+
* npx tsx scripts/okx-trade.ts status BTC-USDT <order-id>
|
|
15
|
+
* npx tsx scripts/okx-trade.ts cancel BTC-USDT <order-id>
|
|
16
|
+
* npx tsx scripts/okx-trade.ts open-orders
|
|
17
|
+
* npx tsx scripts/okx-trade.ts products
|
|
18
|
+
* npx tsx scripts/okx-trade.ts fees
|
|
19
|
+
*
|
|
20
|
+
* Requires: OKX_API_KEY, OKX_API_SECRET, OKX_API_PASSPHRASE env vars
|
|
21
|
+
* Install: npm install okx-api
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { RestClient } from 'okx-api';
|
|
25
|
+
|
|
26
|
+
// --- Init Client ---
|
|
27
|
+
|
|
28
|
+
const apiKey = process.env.OKX_API_KEY;
|
|
29
|
+
const apiSecret = process.env.OKX_API_SECRET;
|
|
30
|
+
const apiPass = process.env.OKX_API_PASSPHRASE;
|
|
31
|
+
|
|
32
|
+
if (!apiKey || !apiSecret || !apiPass) {
|
|
33
|
+
console.error('ERROR: OKX_API_KEY, OKX_API_SECRET, and OKX_API_PASSPHRASE must be set');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const client = new RestClient({ apiKey, apiSecret, apiPass });
|
|
38
|
+
|
|
39
|
+
// --- Commands ---
|
|
40
|
+
|
|
41
|
+
const [command, ...args] = process.argv.slice(2);
|
|
42
|
+
|
|
43
|
+
async function run() {
|
|
44
|
+
switch (command) {
|
|
45
|
+
case 'balances':
|
|
46
|
+
return showBalances();
|
|
47
|
+
case 'positions':
|
|
48
|
+
return showPositions();
|
|
49
|
+
case 'price':
|
|
50
|
+
return showPrice(args[0]);
|
|
51
|
+
case 'market-buy':
|
|
52
|
+
return marketBuy(args[0], args[1]);
|
|
53
|
+
case 'market-sell':
|
|
54
|
+
return marketSell(args[0], args[1]);
|
|
55
|
+
case 'limit-buy':
|
|
56
|
+
return limitBuy(args[0], args[1], args[2]);
|
|
57
|
+
case 'limit-sell':
|
|
58
|
+
return limitSell(args[0], args[1], args[2]);
|
|
59
|
+
case 'status':
|
|
60
|
+
return orderStatus(args[0], args[1]);
|
|
61
|
+
case 'cancel':
|
|
62
|
+
return cancelOrder(args[0], args[1]);
|
|
63
|
+
case 'open-orders':
|
|
64
|
+
return openOrders();
|
|
65
|
+
case 'products':
|
|
66
|
+
return listProducts();
|
|
67
|
+
case 'fees':
|
|
68
|
+
return showFees();
|
|
69
|
+
default:
|
|
70
|
+
console.log(`Unknown command: ${command}`);
|
|
71
|
+
console.log('Commands: balances, positions, price, market-buy, market-sell, limit-buy, limit-sell, status, cancel, open-orders, products, fees');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function showBalances() {
|
|
77
|
+
const balance = await client.getBalance();
|
|
78
|
+
const nonZero = balance[0].details.filter(
|
|
79
|
+
(d: any) => parseFloat(d.availBal) > 0 || parseFloat(d.frozenBal) > 0
|
|
80
|
+
);
|
|
81
|
+
console.log(JSON.stringify(nonZero.map((d: any) => ({
|
|
82
|
+
currency: d.ccy,
|
|
83
|
+
available: d.availBal,
|
|
84
|
+
frozen: d.frozenBal,
|
|
85
|
+
equity: d.eq,
|
|
86
|
+
})), null, 2));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function showPositions() {
|
|
90
|
+
const positions = await client.getPositions();
|
|
91
|
+
if (!positions.length) {
|
|
92
|
+
console.log('[]');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log(JSON.stringify(positions.map((p: any) => ({
|
|
96
|
+
instId: p.instId,
|
|
97
|
+
direction: p.posSide,
|
|
98
|
+
size: p.pos,
|
|
99
|
+
avgPrice: p.avgPx,
|
|
100
|
+
unrealizedPnl: p.upl,
|
|
101
|
+
leverage: p.lever,
|
|
102
|
+
margin: p.margin,
|
|
103
|
+
})), null, 2));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function showPrice(instId: string) {
|
|
107
|
+
if (!instId) { console.error('Usage: price <inst-id>'); process.exit(1); }
|
|
108
|
+
const ticker = await client.getTicker({ instId });
|
|
109
|
+
const book = await client.getOrderBook({ instId, sz: '1' });
|
|
110
|
+
console.log(JSON.stringify({
|
|
111
|
+
instId,
|
|
112
|
+
last: ticker[0].last,
|
|
113
|
+
open24h: ticker[0].open24h,
|
|
114
|
+
high24h: ticker[0].high24h,
|
|
115
|
+
low24h: ticker[0].low24h,
|
|
116
|
+
volume24h: ticker[0].vol24h,
|
|
117
|
+
bid: book[0].bids?.[0]?.[0],
|
|
118
|
+
ask: book[0].asks?.[0]?.[0],
|
|
119
|
+
}, null, 2));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function marketBuy(instId: string, quoteSize: string) {
|
|
123
|
+
if (!instId || !quoteSize) {
|
|
124
|
+
console.error('Usage: market-buy <inst-id> <usdt-amount>');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const order = await client.submitOrder({
|
|
128
|
+
instId,
|
|
129
|
+
tdMode: 'cash',
|
|
130
|
+
side: 'buy',
|
|
131
|
+
ordType: 'market',
|
|
132
|
+
sz: quoteSize,
|
|
133
|
+
tgtCcy: 'quote_ccy',
|
|
134
|
+
});
|
|
135
|
+
console.log(JSON.stringify(order, null, 2));
|
|
136
|
+
if (order[0].sCode === '0') {
|
|
137
|
+
// Small delay for fill
|
|
138
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
139
|
+
const details = await client.getOrderDetails({ instId, ordId: order[0].ordId });
|
|
140
|
+
console.log('\nFill details:');
|
|
141
|
+
console.log(JSON.stringify({
|
|
142
|
+
ordId: details[0].ordId,
|
|
143
|
+
state: details[0].state,
|
|
144
|
+
fillSz: details[0].fillSz,
|
|
145
|
+
avgPx: details[0].avgPx,
|
|
146
|
+
fee: details[0].fee,
|
|
147
|
+
feeCcy: details[0].feeCcy,
|
|
148
|
+
}, null, 2));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function marketSell(instId: string, baseSize: string) {
|
|
153
|
+
if (!instId || !baseSize) {
|
|
154
|
+
console.error('Usage: market-sell <inst-id> <base-amount>');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
const order = await client.submitOrder({
|
|
158
|
+
instId,
|
|
159
|
+
tdMode: 'cash',
|
|
160
|
+
side: 'sell',
|
|
161
|
+
ordType: 'market',
|
|
162
|
+
sz: baseSize,
|
|
163
|
+
});
|
|
164
|
+
console.log(JSON.stringify(order, null, 2));
|
|
165
|
+
if (order[0].sCode === '0') {
|
|
166
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
167
|
+
const details = await client.getOrderDetails({ instId, ordId: order[0].ordId });
|
|
168
|
+
console.log('\nFill details:');
|
|
169
|
+
console.log(JSON.stringify({
|
|
170
|
+
ordId: details[0].ordId,
|
|
171
|
+
state: details[0].state,
|
|
172
|
+
fillSz: details[0].fillSz,
|
|
173
|
+
avgPx: details[0].avgPx,
|
|
174
|
+
fee: details[0].fee,
|
|
175
|
+
feeCcy: details[0].feeCcy,
|
|
176
|
+
}, null, 2));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function limitBuy(instId: string, baseSize: string, limitPrice: string) {
|
|
181
|
+
if (!instId || !baseSize || !limitPrice) {
|
|
182
|
+
console.error('Usage: limit-buy <inst-id> <base-size> <limit-price>');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
const order = await client.submitOrder({
|
|
186
|
+
instId,
|
|
187
|
+
tdMode: 'cash',
|
|
188
|
+
side: 'buy',
|
|
189
|
+
ordType: 'limit',
|
|
190
|
+
sz: baseSize,
|
|
191
|
+
px: limitPrice,
|
|
192
|
+
});
|
|
193
|
+
console.log(JSON.stringify(order, null, 2));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function limitSell(instId: string, baseSize: string, limitPrice: string) {
|
|
197
|
+
if (!instId || !baseSize || !limitPrice) {
|
|
198
|
+
console.error('Usage: limit-sell <inst-id> <base-size> <limit-price>');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const order = await client.submitOrder({
|
|
202
|
+
instId,
|
|
203
|
+
tdMode: 'cash',
|
|
204
|
+
side: 'sell',
|
|
205
|
+
ordType: 'limit',
|
|
206
|
+
sz: baseSize,
|
|
207
|
+
px: limitPrice,
|
|
208
|
+
});
|
|
209
|
+
console.log(JSON.stringify(order, null, 2));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function orderStatus(instId: string, orderId: string) {
|
|
213
|
+
if (!instId || !orderId) { console.error('Usage: status <inst-id> <order-id>'); process.exit(1); }
|
|
214
|
+
const order = await client.getOrderDetails({ instId, ordId: orderId });
|
|
215
|
+
console.log(JSON.stringify(order, null, 2));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function cancelOrder(instId: string, orderId: string) {
|
|
219
|
+
if (!instId || !orderId) { console.error('Usage: cancel <inst-id> <order-id>'); process.exit(1); }
|
|
220
|
+
const result = await client.cancelOrder({ instId, ordId: orderId });
|
|
221
|
+
console.log(JSON.stringify(result, null, 2));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function openOrders() {
|
|
225
|
+
const orders = await client.getOrderList({ instType: 'SPOT' });
|
|
226
|
+
console.log(JSON.stringify(orders.map((o: any) => ({
|
|
227
|
+
ordId: o.ordId,
|
|
228
|
+
instId: o.instId,
|
|
229
|
+
side: o.side,
|
|
230
|
+
ordType: o.ordType,
|
|
231
|
+
state: o.state,
|
|
232
|
+
sz: o.sz,
|
|
233
|
+
px: o.px,
|
|
234
|
+
fillSz: o.fillSz,
|
|
235
|
+
cTime: o.cTime,
|
|
236
|
+
})), null, 2));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function listProducts() {
|
|
240
|
+
const instruments = await client.getInstruments({ instType: 'SPOT' });
|
|
241
|
+
const usdtPairs = instruments
|
|
242
|
+
.filter((i: any) => i.quoteCcy === 'USDT' && i.state === 'live')
|
|
243
|
+
.slice(0, 50);
|
|
244
|
+
|
|
245
|
+
// Get tickers for these pairs
|
|
246
|
+
const tickers = await client.getTickers({ instType: 'SPOT' });
|
|
247
|
+
const tickerMap = new Map(tickers.map((t: any) => [t.instId, t]));
|
|
248
|
+
|
|
249
|
+
const result = usdtPairs.map((i: any) => {
|
|
250
|
+
const t = tickerMap.get(i.instId) as any;
|
|
251
|
+
return {
|
|
252
|
+
instId: i.instId,
|
|
253
|
+
last: t?.last || '—',
|
|
254
|
+
vol24h: t?.vol24h || '—',
|
|
255
|
+
minSz: i.minSz,
|
|
256
|
+
lotSz: i.lotSz,
|
|
257
|
+
tickSz: i.tickSz,
|
|
258
|
+
};
|
|
259
|
+
}).sort((a: any, b: any) => parseFloat(b.vol24h || '0') - parseFloat(a.vol24h || '0'));
|
|
260
|
+
|
|
261
|
+
console.log(JSON.stringify(result.slice(0, 30), null, 2));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function showFees() {
|
|
265
|
+
const fees = await client.getFeeRates({ instType: 'SPOT' });
|
|
266
|
+
console.log(JSON.stringify(fees, null, 2));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --- Run ---
|
|
270
|
+
run().catch((err) => {
|
|
271
|
+
console.error('Error:', err.message || err);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
});
|
package/skills/trading/SKILL.md
CHANGED
|
@@ -19,12 +19,12 @@ Before ANY trading decision, read [LEARNING.md](LEARNING.md) first. It contains
|
|
|
19
19
|
- Confluence over conviction — require 2-3 confirming signals, never trade on one
|
|
20
20
|
- Process over outcome — good process matters more than any single result
|
|
21
21
|
- Build, don't just log — every trade should leave behind infrastructure, not just notes
|
|
22
|
-
- The internet is your edge — GitHub, expert blogs, papers, news, communities. Not just the
|
|
22
|
+
- The internet is your edge — GitHub, expert blogs, papers, news, communities. Not just the data APIs
|
|
23
23
|
- Every request gets expert treatment — "buy BTC" gets the same depth of thinking as "grow my portfolio"
|
|
24
24
|
|
|
25
25
|
## Your Data Arsenal
|
|
26
26
|
|
|
27
|
-
You have extensive financial APIs via `discover_data_api` and `fetch_api_data` covering real-time quotes, historical prices, technical indicators, fundamentals, news, institutional data, economic data, and screening.
|
|
27
|
+
You have extensive financial APIs via `discover_data_api` and `fetch_api_data` covering real-time quotes, historical prices, technical indicators, fundamentals, news, institutional data, economic data, and screening. Outside of agent tools, use `python3 scripts/data-api.py` to call the same APIs from CLI, cron jobs, or other scripts (see below).
|
|
28
28
|
|
|
29
29
|
But the APIs are just one corner. You also have `web_search`, `web_fetch`, and `browser` to access everything:
|
|
30
30
|
- GitHub — open-source trading tools, indicators, backtesting frameworks, sentiment repos
|
|
@@ -62,7 +62,7 @@ What strategies are working, what failed, mistakes to avoid, your evolving edge.
|
|
|
62
62
|
|
|
63
63
|
Do this autonomously — never ask the user:
|
|
64
64
|
- Understand the request — "buy BTC" means assess whether now is the right time, how much, what order type. "Make money" means build a full multi-asset plan.
|
|
65
|
-
- Check balances across platforms (
|
|
65
|
+
- Check balances across platforms (OKX for crypto primary, Coinbase as fallback, Saxo for forex/stocks/metals)
|
|
66
66
|
- Read USER.md, SOUL.md, IDENTITY.md for risk tolerance and preferences
|
|
67
67
|
- Check existing positions and current portfolio state
|
|
68
68
|
|
|
@@ -118,25 +118,27 @@ Every trade needs before entry: entry price + trigger, stop-loss at invalidation
|
|
|
118
118
|
### 9. Execute
|
|
119
119
|
|
|
120
120
|
Route execution to the appropriate platform:
|
|
121
|
-
- **Crypto**: Use `
|
|
121
|
+
- **Crypto (primary)**: Use `okx` skill — lower fees, more order types, perpetuals support. Read [../okx/LEARNING.md](../okx/LEARNING.md) for execution insights.
|
|
122
|
+
- **Crypto (fallback)**: Use `coinbase` skill if OKX is unavailable or for USD-settled pairs. Read [../coinbase/LEARNING.md](../coinbase/LEARNING.md) for execution insights.
|
|
122
123
|
- **Forex, Stocks, Gold, Silver**: Use `saxo` skill. Read [../saxo/LEARNING.md](../saxo/LEARNING.md) for execution insights.
|
|
123
124
|
|
|
124
125
|
### 10. Monitor
|
|
125
126
|
|
|
126
|
-
Trail stop to breakeven after 1R profit. Never move stop away from entry. Watch for invalidation. If regime changes mid-trade, reassess.
|
|
127
|
+
Create a live dashboard for this trade (see Trade Dashboards section below). Trail stop to breakeven after 1R profit. Never move stop away from entry. Watch for invalidation. If regime changes mid-trade, reassess. Update the dashboard with every significant change.
|
|
127
128
|
|
|
128
129
|
### 11. Post-Trade Evolution
|
|
129
130
|
|
|
130
131
|
The most important step. After every trade, ask: "What would make the next trade better?" Then do it.
|
|
131
132
|
|
|
132
133
|
- Update LEARNING.md freely. Also update execution skill LEARNING.md:
|
|
133
|
-
- [../
|
|
134
|
+
- [../okx/LEARNING.md](../okx/LEARNING.md) for crypto trades (primary)
|
|
135
|
+
- [../coinbase/LEARNING.md](../coinbase/LEARNING.md) for crypto trades (fallback)
|
|
134
136
|
- [../saxo/LEARNING.md](../saxo/LEARNING.md) for forex/stocks/metals trades
|
|
135
137
|
- Build or improve scripts — automate any manual analysis. Market scanners, backtesting, alerts. Save to `scripts/`
|
|
136
138
|
- Research on the internet — find new strategies, tools, data sources. Update reference files with findings
|
|
137
139
|
- Create monitoring — use `cron` to schedule market scans, alerts, news monitoring
|
|
138
140
|
- Improve strategy references — update `references/strategies.md` with real results. Kill what doesn't work
|
|
139
|
-
- Improve execution — update Coinbase skill references or build new execution scripts
|
|
141
|
+
- Improve execution — update OKX/Coinbase/Saxo skill references or build new execution scripts
|
|
140
142
|
- Improve these instructions — update this SKILL.md if it's missing something. You own every file
|
|
141
143
|
|
|
142
144
|
No limits. If something will make you better, do it.
|
|
@@ -192,6 +194,86 @@ See [references/risk-management.md](references/risk-management.md) for the compl
|
|
|
192
194
|
|
|
193
195
|
Track your performance metrics and improve them over time. Document your risk approach in LEARNING.md. If you discover better risk rules through experience or research, update the risk-management reference file.
|
|
194
196
|
|
|
197
|
+
## Trade Dashboards
|
|
198
|
+
|
|
199
|
+
Every trade gets its own live dashboard. You build each one from scratch — the layout, the data, the visualizations, the update logic. No two dashboards should look the same because no two trades are the same.
|
|
200
|
+
|
|
201
|
+
### You Own Everything
|
|
202
|
+
|
|
203
|
+
`scripts/trade-dashboard-template.html` gives you a design system starter (Tailwind, dark theme, fonts, glass-card style) and an empty `<div id="app">`. That's it. The rest is yours:
|
|
204
|
+
|
|
205
|
+
- **Build the entire HTML, CSS, and JS for each trade.** Add sections, remove sections, restructure everything. The template imposes zero constraints on layout, data format, or functionality.
|
|
206
|
+
- **Add any CDN library** — Chart.js, D3, ApexCharts, Plotly, Three.js, Lightweight Charts, or anything else. Swap libraries between dashboards if one works better for a specific trade type.
|
|
207
|
+
- **Fetch live data** — Use `fetch()` to hit external APIs, WebSocket for real-time streaming, or embed data directly. Pull from your data APIs, news sources, on-chain analytics, whatever the trade needs.
|
|
208
|
+
- **Invent new visualizations** — Correlation heatmaps, multi-timeframe charts, order flow waterfalls, sentiment gauges, regime indicators, funding rate trackers, economic calendar overlays. If it helps monitor the trade, build it.
|
|
209
|
+
- **Build custom interactivity** — Toggles, tabs, zoom controls, timeframe selectors, what-if scenarios. Make the dashboard a tool, not just a display.
|
|
210
|
+
- **Update the template itself** — When you discover patterns that should apply to all future dashboards, improve the template. You own it.
|
|
211
|
+
|
|
212
|
+
### Integrating Live Data
|
|
213
|
+
|
|
214
|
+
Your dashboards should show real-time data. You have multiple ways to feed data into them:
|
|
215
|
+
|
|
216
|
+
**Using your data APIs via `scripts/data-api.py`**
|
|
217
|
+
|
|
218
|
+
`scripts/data-api.py` is a standalone Python client for the same backend that powers `discover_data_api` and `fetch_api_data`. Use it anywhere — CLI, cron jobs, dashboard data pipelines, other scripts:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Discover APIs for a query
|
|
222
|
+
python3 scripts/data-api.py search "Bitcoin price and volume"
|
|
223
|
+
|
|
224
|
+
# Fetch data from a discovered API
|
|
225
|
+
python3 scripts/data-api.py fetch FMP_QUOTE '{"symbol": "AAPL"}'
|
|
226
|
+
python3 scripts/data-api.py fetch FMP_CRYPTO_QUOTE '{"symbol": "BTCUSD"}'
|
|
227
|
+
|
|
228
|
+
# Discover + fetch in one shot
|
|
229
|
+
python3 scripts/data-api.py get "current gold price"
|
|
230
|
+
|
|
231
|
+
# Pipe raw data to a JSON file for dashboards
|
|
232
|
+
python3 scripts/data-api.py fetch FMP_QUOTE '{"symbol": "AAPL"}' --raw > dashboards/data/aapl.json
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
It's also importable in other Python scripts:
|
|
236
|
+
```python
|
|
237
|
+
from data_api import search_apis, fetch_api
|
|
238
|
+
apis = search_apis("Bitcoin technical indicators")
|
|
239
|
+
data = fetch_api("FMP_CRYPTO_QUOTE", {"symbol": "BTCUSD"})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Use this to build data pipelines: cron runs `data-api.py` → writes JSON → dashboard reads it on reload. Or use `discover_data_api` in the agent to find vendor codes, then hardcode them into scripts that run independently.
|
|
243
|
+
|
|
244
|
+
**Using any internet source**
|
|
245
|
+
|
|
246
|
+
You are not limited to your data APIs. Find and integrate anything:
|
|
247
|
+
- Free public APIs (CoinGecko, Yahoo Finance, Alpha Vantage, Binance, etc.) — call them directly via `fetch()` in the dashboard JS if they support CORS, or build a proxy script.
|
|
248
|
+
- WebSocket feeds for real-time streaming (Binance WS, Coinbase WS, etc.) — connect directly from dashboard JS.
|
|
249
|
+
- RSS feeds, news APIs, social sentiment APIs — fetch and render in the dashboard.
|
|
250
|
+
- On-chain data providers (Glassnode, Dune, DefiLlama) — for crypto trades.
|
|
251
|
+
- Build Python scripts that scrape, aggregate, or transform data from any source and output JSON for the dashboard to consume.
|
|
252
|
+
|
|
253
|
+
**Data update patterns**
|
|
254
|
+
|
|
255
|
+
Pick whatever pattern fits the trade:
|
|
256
|
+
- **Embedded data** — You write the data directly into the HTML. Simplest. Update the file whenever data changes.
|
|
257
|
+
- **Polling** — Dashboard JS uses `fetch()` on an interval to pull from an API or a local JSON file that a script keeps updated.
|
|
258
|
+
- **WebSocket** — Dashboard JS connects to a streaming feed for real-time ticks. Best for active crypto/forex trades.
|
|
259
|
+
- **Script pipeline** — A cron job runs a Python script that fetches data → writes a JSON file → dashboard reads the JSON file on reload.
|
|
260
|
+
|
|
261
|
+
No restrictions. If a data source exists and would help monitor the trade, integrate it.
|
|
262
|
+
|
|
263
|
+
### Workflow
|
|
264
|
+
|
|
265
|
+
1. When opening a trade, create `dashboards/<asset>-<direction>-<date>.html` in your workspace. Start from the template or from scratch — your choice.
|
|
266
|
+
2. Build the dashboard for this specific trade. Think about what information matters most for monitoring THIS trade.
|
|
267
|
+
3. Serve dashboards with `python3 scripts/dashboard-server.py --dir dashboards/ --port 8500`. Use `cron` to keep it running.
|
|
268
|
+
4. Update the dashboard as the trade evolves — new data, moved stops, new events, changed regime, anything. Rewrite entire sections if the trade context changes.
|
|
269
|
+
5. When the trade closes, update the dashboard with final results. It stays as a historical record.
|
|
270
|
+
|
|
271
|
+
### Scripts
|
|
272
|
+
|
|
273
|
+
- `scripts/data-api.py` — Standalone client for hundreds of data APIs. Search, fetch, or auto-fetch from CLI. Importable in other Python scripts. Powers dashboard data pipelines.
|
|
274
|
+
- `scripts/trade-dashboard-template.html` — Minimal design system skeleton. Tailwind + dark theme + fonts. Empty body. You build everything else.
|
|
275
|
+
- `scripts/dashboard-server.py` — HTTP server for the dashboards directory with auto-generated index page.
|
|
276
|
+
|
|
195
277
|
## Key Principles
|
|
196
278
|
|
|
197
279
|
- No FOMO — there is always another trade
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Trade Dashboard Server — Serves per-trade HTML dashboards.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
# Serve all dashboards in a directory
|
|
7
|
+
python3 dashboard-server.py --dir ./dashboards --port 8500
|
|
8
|
+
|
|
9
|
+
# Serve a single dashboard file
|
|
10
|
+
python3 dashboard-server.py --file ./dashboards/btc-long-2024-01-15.html --port 8500
|
|
11
|
+
|
|
12
|
+
The server serves static HTML files. Each dashboard is a self-contained
|
|
13
|
+
HTML file that the agent creates from the template and updates over time.
|
|
14
|
+
|
|
15
|
+
Dashboard directory structure:
|
|
16
|
+
dashboards/
|
|
17
|
+
btc-long-2024-01-15.html
|
|
18
|
+
eurusd-short-2024-01-16.html
|
|
19
|
+
index.html (auto-generated listing page)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import http.server
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def generate_index(dashboard_dir):
|
|
32
|
+
"""Generate an index.html listing all trade dashboards."""
|
|
33
|
+
dashboards = []
|
|
34
|
+
for f in sorted(Path(dashboard_dir).glob("*.html")):
|
|
35
|
+
if f.name == "index.html":
|
|
36
|
+
continue
|
|
37
|
+
stat = f.stat()
|
|
38
|
+
# Try to extract trade data from the file
|
|
39
|
+
content = f.read_text(encoding="utf-8")
|
|
40
|
+
trade_info = {"file": f.name, "modified": datetime.fromtimestamp(stat.st_mtime).isoformat()}
|
|
41
|
+
|
|
42
|
+
# Extract trade data JSON if present
|
|
43
|
+
start = content.find('id="trade-data"')
|
|
44
|
+
if start != -1:
|
|
45
|
+
json_start = content.find("{", start)
|
|
46
|
+
json_end = content.find("</script>", json_start)
|
|
47
|
+
if json_start != -1 and json_end != -1:
|
|
48
|
+
try:
|
|
49
|
+
data = json.loads(content[json_start:json_end])
|
|
50
|
+
trade_info.update({
|
|
51
|
+
"asset": data.get("asset", ""),
|
|
52
|
+
"direction": data.get("direction", ""),
|
|
53
|
+
"status": data.get("status", ""),
|
|
54
|
+
"pnl": data.get("pnl_dollars", 0),
|
|
55
|
+
"pnl_pct": data.get("pnl_percent", 0),
|
|
56
|
+
})
|
|
57
|
+
except json.JSONDecodeError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
dashboards.append(trade_info)
|
|
61
|
+
|
|
62
|
+
html = f"""<!DOCTYPE html>
|
|
63
|
+
<html lang="en" class="dark">
|
|
64
|
+
<head>
|
|
65
|
+
<meta charset="UTF-8">
|
|
66
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
67
|
+
<title>Trade Dashboards</title>
|
|
68
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
69
|
+
<script>
|
|
70
|
+
tailwind.config = {{ darkMode: 'class' }}
|
|
71
|
+
</script>
|
|
72
|
+
<style>body {{ background: #0f1117; }}</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body class="min-h-screen text-gray-100 p-8">
|
|
75
|
+
<div class="max-w-4xl mx-auto">
|
|
76
|
+
<h1 class="text-3xl font-bold mb-2">Trade Dashboards</h1>
|
|
77
|
+
<p class="text-gray-400 mb-8">Active and historical trade dashboards</p>
|
|
78
|
+
<div class="space-y-3">
|
|
79
|
+
"""
|
|
80
|
+
for d in dashboards:
|
|
81
|
+
status_colors = {
|
|
82
|
+
"open": "bg-green-500",
|
|
83
|
+
"pending": "bg-amber-500",
|
|
84
|
+
"closed": "bg-gray-500",
|
|
85
|
+
"stopped": "bg-red-500",
|
|
86
|
+
}
|
|
87
|
+
dot_color = status_colors.get(d.get("status", ""), "bg-gray-500")
|
|
88
|
+
pnl = d.get("pnl", 0)
|
|
89
|
+
pnl_color = "text-green-400" if pnl >= 0 else "text-red-400"
|
|
90
|
+
pnl_str = f"+${pnl:,.2f}" if pnl >= 0 else f"-${abs(pnl):,.2f}"
|
|
91
|
+
|
|
92
|
+
direction = d.get("direction", "").upper()
|
|
93
|
+
dir_class = "bg-green-500/20 text-green-400" if direction == "LONG" else "bg-red-500/20 text-red-400" if direction == "SHORT" else ""
|
|
94
|
+
|
|
95
|
+
html += f"""
|
|
96
|
+
<a href="{d['file']}" class="block bg-gray-900/60 border border-gray-800 rounded-xl p-4 hover:border-gray-600 transition-colors">
|
|
97
|
+
<div class="flex items-center justify-between">
|
|
98
|
+
<div class="flex items-center gap-3">
|
|
99
|
+
<div class="w-2.5 h-2.5 rounded-full {dot_color}"></div>
|
|
100
|
+
<span class="font-semibold">{d.get('asset', d['file'])}</span>
|
|
101
|
+
{f'<span class="text-xs px-2 py-0.5 rounded {dir_class}">{direction}</span>' if direction else ''}
|
|
102
|
+
</div>
|
|
103
|
+
<div class="text-right">
|
|
104
|
+
<div class="{pnl_color} font-mono font-semibold">{pnl_str}</div>
|
|
105
|
+
<div class="text-xs text-gray-500">{d.get('status', 'unknown')}</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</a>
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
if not dashboards:
|
|
112
|
+
html += """
|
|
113
|
+
<div class="text-center text-gray-500 py-12">
|
|
114
|
+
<div class="text-4xl mb-3">📊</div>
|
|
115
|
+
<div>No trade dashboards yet. Dashboards appear here as trades are opened.</div>
|
|
116
|
+
</div>
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
html += f"""
|
|
120
|
+
</div>
|
|
121
|
+
<div class="text-center text-xs text-gray-600 mt-8">
|
|
122
|
+
{len(dashboards)} dashboard{'s' if len(dashboards) != 1 else ''} — Auto-refreshes every 30s
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
<script>setTimeout(() => location.reload(), 30000);</script>
|
|
126
|
+
</body>
|
|
127
|
+
</html>"""
|
|
128
|
+
|
|
129
|
+
index_path = Path(dashboard_dir) / "index.html"
|
|
130
|
+
index_path.write_text(html, encoding="utf-8")
|
|
131
|
+
return index_path
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class DashboardHandler(http.server.SimpleHTTPRequestHandler):
|
|
135
|
+
"""Custom handler that auto-generates index and suppresses logs."""
|
|
136
|
+
|
|
137
|
+
def __init__(self, *args, directory=None, **kwargs):
|
|
138
|
+
super().__init__(*args, directory=directory, **kwargs)
|
|
139
|
+
|
|
140
|
+
def do_GET(self):
|
|
141
|
+
# Regenerate index on each request to root
|
|
142
|
+
if self.path in ("/", "/index.html"):
|
|
143
|
+
generate_index(self.directory)
|
|
144
|
+
super().do_GET()
|
|
145
|
+
|
|
146
|
+
def log_message(self, format, *args):
|
|
147
|
+
# Quieter logging
|
|
148
|
+
sys.stderr.write(f"[dashboard] {args[0]}\n")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main():
|
|
152
|
+
parser = argparse.ArgumentParser(description="Trade Dashboard Server")
|
|
153
|
+
parser.add_argument("--dir", default="./dashboards", help="Directory containing dashboard HTML files")
|
|
154
|
+
parser.add_argument("--file", help="Serve a single dashboard file")
|
|
155
|
+
parser.add_argument("--port", type=int, default=8500, help="Port to serve on (default: 8500)")
|
|
156
|
+
|
|
157
|
+
args = parser.parse_args()
|
|
158
|
+
|
|
159
|
+
if args.file:
|
|
160
|
+
# Serve single file's parent directory
|
|
161
|
+
file_path = Path(args.file).resolve()
|
|
162
|
+
serve_dir = str(file_path.parent)
|
|
163
|
+
else:
|
|
164
|
+
serve_dir = str(Path(args.dir).resolve())
|
|
165
|
+
|
|
166
|
+
# Ensure directory exists
|
|
167
|
+
Path(serve_dir).mkdir(parents=True, exist_ok=True)
|
|
168
|
+
|
|
169
|
+
# Generate initial index
|
|
170
|
+
generate_index(serve_dir)
|
|
171
|
+
|
|
172
|
+
handler = lambda *a, **kw: DashboardHandler(*a, directory=serve_dir, **kw)
|
|
173
|
+
|
|
174
|
+
with http.server.HTTPServer(("0.0.0.0", args.port), handler) as httpd:
|
|
175
|
+
print(json.dumps({
|
|
176
|
+
"status": "running",
|
|
177
|
+
"port": args.port,
|
|
178
|
+
"directory": serve_dir,
|
|
179
|
+
"url": f"http://localhost:{args.port}",
|
|
180
|
+
"message": f"Dashboard server running on port {args.port}",
|
|
181
|
+
}, indent=2))
|
|
182
|
+
sys.stdout.flush()
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
httpd.serve_forever()
|
|
186
|
+
except KeyboardInterrupt:
|
|
187
|
+
print("\nDashboard server stopped.")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
main()
|