feat: order management (cancel/amend), stale order cleanup before market close, enriched LLM reasoning (RSI+BTC+regime), fleet dashboard with SVG charts/gauges/donuts

This commit is contained in:
drjones
2026-08-02 20:10:24 -07:00
parent 8e860361ec
commit c817216ab1
9 changed files with 301 additions and 26 deletions

Binary file not shown.

152
bot.log
View File

@@ -1752,3 +1752,155 @@ sqlite3.OperationalError: no such column: id
2026-08-02 19:23:11,364 skip KXXRP15M-26AUG022230-30: no swing (0.000% < 0.005%) 2026-08-02 19:23:11,364 skip KXXRP15M-26AUG022230-30: no swing (0.000% < 0.005%)
2026-08-02 19:23:40,530 skip KXBTC15M-26AUG022230-30: no swing (0.000% < 0.005%) 2026-08-02 19:23:40,530 skip KXBTC15M-26AUG022230-30: no swing (0.000% < 0.005%)
2026-08-02 19:25:31,546 POST YES x1 KXDOGE15M-26AUG022230-30 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP 2026-08-02 19:25:31,546 POST YES x1 KXDOGE15M-26AUG022230-30 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:26:03,056 POST YES x1 KXSOL15M-26AUG022230-30 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:26:13,916 HEDGE KXBTC15M-TEST: no@45¢ + yes@42¢ → locked 13¢
2026-08-02 19:26:40,081 POST NO x1 KXETH15M-26AUG022230-30 @ 50¢ — DOWN: UP spike 0.05% conf=0.78 [RSI 17 oversold + BTC bearish macro, fade the spike]
2026-08-02 19:27:08,889 POST YES x1 KXDOGE15M-26AUG022230-30 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:27:22,410 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 19:27:27,546 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 19:27:27,546 staggering start by 20s
2026-08-02 19:27:32,423 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 19:27:32,423 staggering start by 40s
2026-08-02 19:27:36,308 POST YES x1 KXDOGE15M-26AUG022230-30 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:27:37,723 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 19:27:37,723 staggering start by 60s
2026-08-02 19:27:42,716 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 19:27:42,716 staggering start by 80s
2026-08-02 19:28:00,714 POST YES x1 KXSOL15M-26AUG022230-30 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:28:36,825 POST YES x1 KXETH15M-26AUG022230-30 @ 50¢ — ⚠ RSI 6 extreme oversold → fade UP
2026-08-02 19:28:44,817 POST YES x1 KXXRP15M-26AUG022230-30 @ 50¢ — ⚠ RSI 3 extreme oversold → fade UP
2026-08-02 19:29:13,720 POST YES x1 KXBTC15M-26AUG022230-30 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:32:41,278 POST YES x1 KXDOGE15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:33:04,823 skip KXSOL15M-26AUG022245-45: passes: RSI(5m)=32 oversold, trend all negative — no clear fade edge yet
2026-08-02 19:33:33,082 skip KXETH15M-26AUG022245-45: no swing (0.005% < 0.005%)
2026-08-02 19:33:42,876 POST YES x1 KXXRP15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:34:12,848 POST YES x1 KXBTC15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:37:39,640 POST YES x1 KXDOGE15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:38:10,615 skip KXSOL15M-26AUG022245-45: no swing (0.003% < 0.005%)
2026-08-02 19:38:46,022 POST YES x1 KXXRP15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:38:47,212 POST NO x1 KXETH15M-26AUG022245-45 @ 50¢ — DOWN: DOWN spike 0.01% conf=0.65 [Sharp 1h/2h decline, RSI(5m)=44 neutral, fading short-term w]
2026-08-02 19:39:15,077 POST YES x1 KXBTC15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:42:49,063 POST YES x1 KXDOGE15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:43:09,475 skip KXSOL15M-26AUG022245-45: passes: RSI neutral, choppy regime, no clear edge
2026-08-02 19:43:44,491 POST YES x1 KXETH15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:43:45,014 POST YES x1 KXXRP15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:44:13,261 POST YES x1 KXBTC15M-26AUG022245-45 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:47:51,999 POST NO x1 KXDOGE15M-26AUG022300-00 @ 50¢ — DOWN: UP spike 0.06% conf=0.65 [RSI(5m)=76 overbought, fading sharp UP spike]
2026-08-02 19:48:16,137 skip KXSOL15M-26AUG022300-00: passes: RSI neutral, no clear spike to fade
2026-08-02 19:48:48,144 POST YES x1 KXXRP15M-26AUG022300-00 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:49:02,411 POST YES x1 KXETH15M-26AUG022300-00 @ 50¢ — ⚠ RSI 2 extreme oversold → fade UP
2026-08-02 19:49:13,880 POST YES x1 KXBTC15M-26AUG022300-00 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:52:31,790 127.0.0.1 - - [02/Aug/2026 19:52:31] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:52:44,887 127.0.0.1 - - [02/Aug/2026 19:52:44] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:52:45,024 127.0.0.1 - - [02/Aug/2026 19:52:45] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:52:45,026 127.0.0.1 - - [02/Aug/2026 19:52:45] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:52:45,530 127.0.0.1 - - [02/Aug/2026 19:52:45] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:52:45,696 127.0.0.1 - - [02/Aug/2026 19:52:45] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:52:51,875 POST YES x1 KXDOGE15M-26AUG022300-00 @ 50¢ — ⚠ RSI 12 extreme oversold → fade UP
2026-08-02 19:52:59,901 127.0.0.1 - - [02/Aug/2026 19:52:59] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:52:59,993 127.0.0.1 - - [02/Aug/2026 19:52:59] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:53:00,479 127.0.0.1 - - [02/Aug/2026 19:53:00] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:53:14,923 127.0.0.1 - - [02/Aug/2026 19:53:14] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:53:15,106 127.0.0.1 - - [02/Aug/2026 19:53:15] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:53:15,649 127.0.0.1 - - [02/Aug/2026 19:53:15] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:53:20,660 POST NO x1 KXSOL15M-26AUG022300-00 @ 50¢ — DOWN: DOWN spike 0.05% conf=0.65 [RSI(1m)=17 oversold, sharp down trend fading into mean rever]
2026-08-02 19:53:29,962 127.0.0.1 - - [02/Aug/2026 19:53:29] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:53:30,291 127.0.0.1 - - [02/Aug/2026 19:53:30] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:53:30,293 127.0.0.1 - - [02/Aug/2026 19:53:30] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:53:30,772 127.0.0.1 - - [02/Aug/2026 19:53:30] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:53:30,877 127.0.0.1 - - [02/Aug/2026 19:53:30] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:53:45,003 127.0.0.1 - - [02/Aug/2026 19:53:44] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:53:45,178 127.0.0.1 - - [02/Aug/2026 19:53:45] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:53:45,190 127.0.0.1 - - [02/Aug/2026 19:53:45] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:53:45,714 127.0.0.1 - - [02/Aug/2026 19:53:45] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:53:46,990 POST YES x1 KXXRP15M-26AUG022300-00 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:54:00,025 127.0.0.1 - - [02/Aug/2026 19:54:00] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:54:00,151 127.0.0.1 - - [02/Aug/2026 19:54:00] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:54:00,681 127.0.0.1 - - [02/Aug/2026 19:54:00] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:54:00,960 POST YES x1 KXETH15M-26AUG022300-00 @ 50¢ — ⚠ RSI 1 extreme oversold → fade UP
2026-08-02 19:54:14,045 POST YES x1 KXBTC15M-26AUG022300-00 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:54:15,049 127.0.0.1 - - [02/Aug/2026 19:54:15] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:54:15,171 127.0.0.1 - - [02/Aug/2026 19:54:15] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:54:15,176 127.0.0.1 - - [02/Aug/2026 19:54:15] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:54:15,676 127.0.0.1 - - [02/Aug/2026 19:54:15] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:54:30,067 127.0.0.1 - - [02/Aug/2026 19:54:30] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:54:30,185 127.0.0.1 - - [02/Aug/2026 19:54:30] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:54:30,684 127.0.0.1 - - [02/Aug/2026 19:54:30] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:58:03,216 POST YES x1 KXDOGE15M-26AUG022300-00 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:58:22,247 POST NO x1 KXSOL15M-26AUG022300-00 @ 50¢ — DOWN: DOWN spike 0.10% conf=0.65 [RSI 25 oversold, sharp down trend, fade the dip]
2026-08-02 19:58:29,790 127.0.0.1 - - [02/Aug/2026 19:58:29] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:58:30,141 127.0.0.1 - - [02/Aug/2026 19:58:30] "GET / HTTP/1.1" 200 -
2026-08-02 19:58:30,972 127.0.0.1 - - [02/Aug/2026 19:58:30] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:58:35,897 127.0.0.1 - - [02/Aug/2026 19:58:35] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:58:40,749 127.0.0.1 - - [02/Aug/2026 19:58:40] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:58:45,983 127.0.0.1 - - [02/Aug/2026 19:58:45] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:58:50,155 skip KXXRP15M-26AUG022300-00: passes: RSI neutral, no clear spike to fade
2026-08-02 19:58:50,837 127.0.0.1 - - [02/Aug/2026 19:58:50] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:58:54,839 127.0.0.1 - - [02/Aug/2026 19:58:54] "GET / HTTP/1.1" 200 -
2026-08-02 19:58:55,834 127.0.0.1 - - [02/Aug/2026 19:58:55] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:00,536 127.0.0.1 - - [02/Aug/2026 19:59:00] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:05,465 127.0.0.1 - - [02/Aug/2026 19:59:05] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:10,574 127.0.0.1 - - [02/Aug/2026 19:59:10] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:12,476 POST NO x1 KXETH15M-26AUG022300-00 @ 50¢ — DOWN: DOWN spike 0.16% conf=0.65 [RSI oversold + BTC bearish, fade the sharp down move]
2026-08-02 19:59:15,563 127.0.0.1 - - [02/Aug/2026 19:59:15] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:16,460 127.0.0.1 - - [02/Aug/2026 19:59:16] "GET / HTTP/1.1" 200 -
2026-08-02 19:59:17,231 127.0.0.1 - - [02/Aug/2026 19:59:17] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:17,760 127.0.0.1 - - [02/Aug/2026 19:59:17] "GET /fleept HTTP/1.1" 404 -
2026-08-02 19:59:18,167 POST YES x1 KXBTC15M-26AUG022300-00 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 19:59:22,646 127.0.0.1 - - [02/Aug/2026 19:59:22] "GET /fleet HTTP/1.1" 200 -
2026-08-02 19:59:22,961 127.0.0.1 - - [02/Aug/2026 19:59:22] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:59:23,399 127.0.0.1 - - [02/Aug/2026 19:59:23] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:23,428 127.0.0.1 - - [02/Aug/2026 19:59:23] "GET /api/fleet/ticks?coin=btc&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:23,447 127.0.0.1 - - [02/Aug/2026 19:59:23] "GET /api/fleet/ticks?coin=doge&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:23,457 127.0.0.1 - - [02/Aug/2026 19:59:23] "GET /api/fleet/ticks?coin=eth&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:23,466 127.0.0.1 - - [02/Aug/2026 19:59:23] "GET /api/fleet/ticks?coin=sol&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:23,476 127.0.0.1 - - [02/Aug/2026 19:59:23] "GET /api/fleet/ticks?coin=xrp&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:37,853 127.0.0.1 - - [02/Aug/2026 19:59:37] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:59:38,402 127.0.0.1 - - [02/Aug/2026 19:59:38] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:38,425 127.0.0.1 - - [02/Aug/2026 19:59:38] "GET /api/fleet/ticks?coin=btc&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:38,444 127.0.0.1 - - [02/Aug/2026 19:59:38] "GET /api/fleet/ticks?coin=doge&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:38,453 127.0.0.1 - - [02/Aug/2026 19:59:38] "GET /api/fleet/ticks?coin=eth&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:38,464 127.0.0.1 - - [02/Aug/2026 19:59:38] "GET /api/fleet/ticks?coin=sol&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:38,473 127.0.0.1 - - [02/Aug/2026 19:59:38] "GET /api/fleet/ticks?coin=xrp&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:52,895 127.0.0.1 - - [02/Aug/2026 19:59:52] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 19:59:53,518 127.0.0.1 - - [02/Aug/2026 19:59:53] "GET /api/state HTTP/1.1" 200 -
2026-08-02 19:59:53,545 127.0.0.1 - - [02/Aug/2026 19:59:53] "GET /api/fleet/ticks?coin=btc&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:53,561 127.0.0.1 - - [02/Aug/2026 19:59:53] "GET /api/fleet/ticks?coin=doge&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:53,576 127.0.0.1 - - [02/Aug/2026 19:59:53] "GET /api/fleet/ticks?coin=eth&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:53,585 127.0.0.1 - - [02/Aug/2026 19:59:53] "GET /api/fleet/ticks?coin=sol&mins=60 HTTP/1.1" 200 -
2026-08-02 19:59:53,596 127.0.0.1 - - [02/Aug/2026 19:59:53] "GET /api/fleet/ticks?coin=xrp&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:07,883 127.0.0.1 - - [02/Aug/2026 20:00:07] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 20:00:08,384 127.0.0.1 - - [02/Aug/2026 20:00:08] "GET /api/state HTTP/1.1" 200 -
2026-08-02 20:00:08,426 127.0.0.1 - - [02/Aug/2026 20:00:08] "GET /api/fleet/ticks?coin=btc&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:08,453 127.0.0.1 - - [02/Aug/2026 20:00:08] "GET /api/fleet/ticks?coin=doge&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:08,471 127.0.0.1 - - [02/Aug/2026 20:00:08] "GET /api/fleet/ticks?coin=eth&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:08,490 127.0.0.1 - - [02/Aug/2026 20:00:08] "GET /api/fleet/ticks?coin=sol&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:08,499 127.0.0.1 - - [02/Aug/2026 20:00:08] "GET /api/fleet/ticks?coin=xrp&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:24,386 127.0.0.1 - - [02/Aug/2026 20:00:24] "GET /api/fleet HTTP/1.1" 200 -
2026-08-02 20:00:25,628 127.0.0.1 - - [02/Aug/2026 20:00:25] "GET /api/state HTTP/1.1" 200 -
2026-08-02 20:00:25,694 127.0.0.1 - - [02/Aug/2026 20:00:25] "GET /api/fleet/ticks?coin=btc&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:25,718 127.0.0.1 - - [02/Aug/2026 20:00:25] "GET /api/fleet/ticks?coin=doge&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:25,828 127.0.0.1 - - [02/Aug/2026 20:00:25] "GET /api/fleet/ticks?coin=eth&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:25,843 127.0.0.1 - - [02/Aug/2026 20:00:25] "GET /api/fleet/ticks?coin=sol&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:25,867 127.0.0.1 - - [02/Aug/2026 20:00:25] "GET /api/fleet/ticks?coin=xrp&mins=60 HTTP/1.1" 200 -
2026-08-02 20:00:32,477 127.0.0.1 - - [02/Aug/2026 20:00:32] "GET /fleet HTTP/1.1" 200 -
2026-08-02 20:03:01,378 POST NO x1 KXDOGE15M-26AUG022315-15 @ 50¢ — DOWN: DOWN spike 0.07% conf=0.65 [RSI(5m)=28 oversold, 15m momentum down]
2026-08-02 20:03:21,785 POST NO x1 KXSOL15M-26AUG022315-15 @ 50¢ — ⚠ RSI 100 extreme overbought → fade DOWN
2026-08-02 20:03:47,237 skip KXXRP15M-26AUG022315-15: passes: Fear regime + choppy market, no clear edge
2026-08-02 20:04:10,746 POST NO x1 KXETH15M-26AUG022315-15 @ 50¢ — DOWN: UP spike 0.02% conf=0.65 [RSI(1m)=73 overbought, fading sharp UP spike]
2026-08-02 20:04:14,890 POST NO x1 KXBTC15M-26AUG022315-15 @ 50¢ — ⚠ RSI 100 extreme overbought → fade DOWN
2026-08-02 20:06:27,737 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 20:06:32,449 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 20:06:32,449 staggering start by 20s
2026-08-02 20:06:37,430 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 20:06:37,431 staggering start by 40s
2026-08-02 20:06:42,382 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 20:06:42,383 staggering start by 60s
2026-08-02 20:06:47,862 K4LSH1_OPS online — mode=AUTO dry_run=False stake=10¢ max_spend=100¢
2026-08-02 20:06:47,864 staggering start by 80s
2026-08-02 20:06:52,051 skip KXDOGE15M-26AUG022315-15: passes: Choppy regime, no clear edge
2026-08-02 20:07:13,661 skip KXSOL15M-26AUG022315-15: passes: RSI neutral, no clear spike to fade
2026-08-02 20:07:54,193 POST NO x1 KXETH15M-26AUG022315-15 @ 50¢ — DOWN: DOWN spike 0.11% conf=0.65 [Sharp 2h downtrend + RSI(35) neutral, fading short-term dip ]
2026-08-02 20:07:54,850 POST YES x1 KXXRP15M-26AUG022315-15 @ 50¢ — ⚠ RSI 4 extreme oversold → fade UP
2026-08-02 20:08:27,463 POST YES x1 KXBTC15M-26AUG022315-15 @ 50¢ — ⚠ RSI 0 extreme oversold → fade UP
2026-08-02 20:10:05,986 127.0.0.1 - - [02/Aug/2026 20:10:05] "GET /api/fleet HTTP/1.1" 200 -

30
bot.py
View File

@@ -97,6 +97,15 @@ class Kalshi:
"post_only": False, "reduce_only": False} "post_only": False, "reduce_only": False}
if dry: return {"simulated": True, "order": body} if dry: return {"simulated": True, "order": body}
return self.req("POST", "/portfolio/events/orders", json=body) return self.req("POST", "/portfolio/events/orders", json=body)
def cancel_order(self, order_id, dry=False):
"""Cancel an open order by ID."""
if dry: return {"simulated": True, "cancelled": order_id}
return self.req("DELETE", f"/portfolio/events/orders/{order_id}")
def amend_order(self, order_id, new_price_cents, dry=False):
"""Amend an existing order's price."""
body = {"price": f"{new_price_cents/100:.4f}"}
if dry: return {"simulated": True, "amended": order_id}
return self.req("POST", f"/portfolio/events/orders/{order_id}/amend", json=body)
# ───────── btc price feed ───────── # ───────── btc price feed ─────────
def btc_price(coin="BTC"): def btc_price(coin="BTC"):
@@ -575,6 +584,27 @@ def run():
try: try:
resolve_bets(kx, conn) resolve_bets(kx, conn)
adapt_controls(conn, cfg) adapt_controls(conn, cfg)
# cancel stale unfilled orders close to market expiration
try:
open_orders = conn.execute(
"SELECT id, ticker, order_id, side, price_cents FROM orders WHERE status IN ('placed','posted') AND dry=0"
).fetchall()
for oid, ticker, koid, side, px in open_orders:
try:
m = kx.req("GET", f"/markets/{ticker}")
ct = m.get("close_time","")
if ct:
close_ts = datetime.fromisoformat(ct.replace("Z","+00:00")).timestamp()
mins_left = (close_ts - time.time())/60
if mins_left < 1.5:
kx.cancel_order(koid, cfg.get("dry_run", False))
conn.execute("UPDATE orders SET status='cancelled' WHERE id=?", (oid,))
conn.commit()
log_event(conn, "info", f"CANCEL stale {ticker} {side}@{px}¢ (mins_left={mins_left:.1f})")
except Exception:
pass
except Exception:
pass
p = btc_price(cfg.get("coin", "BTC")) p = btc_price(cfg.get("coin", "BTC"))
if p: if p:
conn.execute("INSERT INTO ticks VALUES (?,?)", (time.time(), p)) conn.execute("INSERT INTO ticks VALUES (?,?)", (time.time(), p))

BIN
btc.db

Binary file not shown.

View File

@@ -169,7 +169,6 @@ refresh();setInterval(refresh,5000);
</script></body></html>""" </script></body></html>"""
FLEET_HTML = r"""<!DOCTYPE html><html><head><meta charset="utf-8"><title>K4LSH1 FLEET</title> FLEET_HTML = r"""<!DOCTYPE html><html><head><meta charset="utf-8"><title>K4LSH1 FLEET</title>
<meta http-equiv="refresh" content="15">
<style> <style>
*{margin:0;padding:0;box-sizing:border-box} *{margin:0;padding:0;box-sizing:border-box}
body{background:#020603;color:#00ff41;font-family:'SF Mono',Menlo,monospace;overflow-x:hidden} body{background:#020603;color:#00ff41;font-family:'SF Mono',Menlo,monospace;overflow-x:hidden}
@@ -177,61 +176,138 @@ body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:99;ba
.glow{text-shadow:0 0 8px #00ff41,0 0 22px #00ff4133} .glow{text-shadow:0 0 8px #00ff41,0 0 22px #00ff4133}
header{display:flex;align-items:center;justify-content:space-between;padding:10px 18px;border-bottom:1px solid #00ff4133;background:#031007} header{display:flex;align-items:center;justify-content:space-between;padding:10px 18px;border-bottom:1px solid #00ff4133;background:#031007}
h1{font-size:1rem;letter-spacing:3px}.dim{color:#0a5f2e} h1{font-size:1rem;letter-spacing:3px}.dim{color:#0a5f2e}
.fleet{display:grid;grid-template-columns:repeat(auto-fit,minmax(400px,1fr));gap:10px;padding:10px 18px} .topbar{display:flex;gap:14px;padding:10px 18px;border-bottom:1px solid #00ff4122;background:#020804}
.metric{text-align:center;min-width:80px}
.metric .val{font-size:1.3rem;font-weight:800}
.metric .lab{font-size:.55rem;color:#0a5f2e;letter-spacing:1px}
.fleet{display:grid;grid-template-columns:repeat(auto-fit,minmax(440px,1fr));gap:10px;padding:10px 18px}
.card{border:1px solid #00ff4144;background:#031007cc;border-radius:8px;padding:12px;box-shadow:0 0 18px #00ff4112 inset} .card{border:1px solid #00ff4144;background:#031007cc;border-radius:8px;padding:12px;box-shadow:0 0 18px #00ff4112 inset}
.card h2{font-size:.85rem;letter-spacing:2px;margin-bottom:8px} .card h2{font-size:.85rem;letter-spacing:2px;margin-bottom:6px}
.big{font-size:2rem;font-weight:800} .big{font-size:1.8rem;font-weight:800}
.small{font-size:.68rem;color:#2ea043} .small{font-size:.68rem;color:#2ea043}
.row{display:flex;justify-content:space-between;font-size:.72rem;padding:2px 0;border-bottom:1px dashed #00ff4122} .row{display:flex;justify-content:space-between;font-size:.7rem;padding:2px 0;border-bottom:1px dashed #00ff4122}
.row:last-child{border:none} .row:last-child{border:none}
.up{color:#00ff41}.dn{color:#ff3131;text-shadow:0 0 8px #ff313166}.skip{color:#0a5f2e} .up{color:#00ff41}.dn{color:#ff3131;text-shadow:0 0 8px #ff313166}.skip{color:#0a5f2e}
.dec{margin:2px 0;padding:3px 0;border-bottom:1px dashed #00ff4118;font-size:.7rem;line-height:1.5}
.dec .why{color:#0a5f2e;font-size:.62rem}
.dec .llm{color:#2ea043;font-size:.62rem;margin-top:1px}
.badge{display:inline-block;padding:1px 6px;border:1px solid;border-radius:3px;font-size:.6rem} .badge{display:inline-block;padding:1px 6px;border:1px solid;border-radius:3px;font-size:.6rem}
.badge.up{border-color:#00ff41;color:#00ff41} .badge.up{border-color:#00ff41;color:#00ff41}.badge.dn{border-color:#ff3131;color:#ff3131}.badge.skip{border-color:#0a5f2e;color:#0a5f2e}
.badge.dn{border-color:#ff3131;color:#ff3131} .chart-row{display:flex;gap:8px;align-items:flex-end;margin:6px 0}
.badge.skip{border-color:#0a5f2e;color:#0a5f2e} .chart-label{font-size:.55rem;color:#0a5f2e;writing-mode:vertical-rl;transform:rotate(180deg);text-align:center;min-width:12px}
#statusbar{position:fixed;bottom:0;left:0;right:0;z-index:100;display:flex;background:#010502f2;border-top:1px solid #00ff4166;backdrop-filter:blur(8px);font-size:.68rem;padding:0} svg{display:block;flex:1}
#statusbar .cell{padding:6px 12px;border-right:1px solid #00ff4122;white-space:nowrap} .dec{margin:2px 0;padding:3px 0;border-bottom:1px dashed #00ff4118;font-size:.68rem;line-height:1.5}
#statusbar .lab{color:#0a5f2e;font-size:.55rem;letter-spacing:1px;display:block} .dec .why{color:#0a5f2e;font-size:.6rem}
.dec .llm{color:#2ea043;font-size:.6rem;margin-top:1px}
.heatmap{display:grid;grid-template-columns:repeat(24,1fr);gap:1px;margin:4px 0}
.heat{aspect-ratio:1;border-radius:2px;background:#031007}
#statusbar{position:fixed;bottom:0;left:0;right:0;z-index:100;display:flex;background:#010502f2;border-top:1px solid #00ff4166;backdrop-filter:blur(8px);font-size:.65rem;padding:0}
#statusbar .cell{padding:5px 10px;border-right:1px solid #00ff4122;white-space:nowrap}
#statusbar .lab{color:#0a5f2e;font-size:.5rem;letter-spacing:1px;display:block}
#statusbar .cell b{color:#00ff41} #statusbar .cell b{color:#00ff41}
.gauge-wrap{display:flex;align-items:center;gap:8px;margin:4px 0}
.gauge{flex:1;height:8px;background:#031007;border-radius:4px;overflow:hidden;position:relative}
.gauge-fill{height:100%;border-radius:4px;transition:width .3s}
.gauge-label{font-size:.6rem;min-width:28px;text-align:right}
.donut-wrap{display:flex;align-items:center;gap:8px}
.donut-lab{font-size:.6rem;color:#2ea043}
</style></head><body> </style></head><body>
<header><h1 class="glow">K4LSH1<span class="dim">_FLEET</span></h1><span id="tot"></span></header> <header><h1 class="glow">K4LSH1<span class="dim">_FLEET</span></h1><span id="clock"></span></header>
<div class="topbar" id="topbar"></div>
<div class="fleet" id="cards"></div> <div class="fleet" id="cards"></div>
<div id="statusbar"></div> <div id="statusbar"></div>
<script> <script>
const C={DOGE:"#c2a633",SOL:"#9945FF",ETH:"#627EEA",XRP:"#00AAE4",BTC:"#f7931a"}; const C={DOGE:"#c2a633",SOL:"#9945FF",ETH:"#627EEA",XRP:"#00AAE4",BTC:"#f7931a"};
function fmt(c){return c<0?`<span class="dn">-${Math.abs(c)}¢</span>`:c>0?`<span class="up">+${c}¢</span>`:`<span class="skip">${c}¢</span>`} function fmt(c){return c<0?`<span class="dn">-${Math.abs(c)}¢</span>`:c>0?`<span class="up">+${c}¢</span>`:`<span class="skip">${c}¢</span>`}
function ago(ts){const d=Date.now()/1000-ts;return d<60?Math.floor(d)+'s':d<3600?Math.floor(d/60)+'m':Math.floor(d/3600)+'h'} function ago(ts){const d=Date.now()/1000-ts;return d<60?Math.floor(d)+'s':d<3600?Math.floor(d/60)+'m':Math.floor(d/3600)+'h'}
function sparkline(points,color,w=200,h=40){
if(!points||points.length<2)return `<svg width="${w}" height="${h}"></svg>`;
const min=Math.min(...points),max=Math.max(...points),range=max-min||1;
const pts=points.map((p,i)=>`${(i/(points.length-1))*w},${h-((p-min)/range)*(h-4)-2}`).join(' ');
return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<polyline fill="none" stroke="${color}" stroke-width="1.5" points="${pts}" opacity=".8"/>
<circle cx="${pts.split(' ').pop().split(',')[0]}" cy="${pts.split(' ').pop().split(',')[1]}" r="2.5" fill="${color}"/>
</svg>`;
}
function gauge(val,color){
const pct=Math.max(0,Math.min(100,val));
const fill=pct>80?'#ff3131':pct>60?'#ffb000':pct>40?'#00ff41':'#00ff41';
return `<div class="gauge-wrap"><div class="gauge"><div class="gauge-fill" style="width:${pct}%;background:${fill}"></div></div><span class="gauge-label">${val?.toFixed(0)||'-'}</span></div>`;
}
function donut(won,total){
const losses=total-won,pct=total>0?won/total:0;
const angle=pct*360,r=16,cx=20,cy=20;
const x1=cx+r*Math.cos(-Math.PI/2),y1=cy+r*Math.sin(-Math.PI/2);
const x2=cx+r*Math.cos(-Math.PI/2+angle*Math.PI/180),y2=cy+r*Math.sin(-Math.PI/2+angle*Math.PI/180);
const large=angle>180?1:0;
return `<div class="donut-wrap">
<svg width="40" height="40">
<circle cx="${cx}" cy="${cy}" r="${r}" fill="#031007" stroke="#0a5f2e" stroke-width="2"/>
${angle>0?`<path d="M${cx},${cy} L${x1},${y1} A${r},${r} 0 ${large} 1 ${x2},${y2} Z" fill="#00ff41" opacity=".7"/>`:''}
${angle<360?`<path d="M${cx},${cy} L${x2},${y2} A${r},${r} 0 ${large} 0 ${x1},${y1} Z" fill="#ff3131" opacity=".5"/>`:''}
<text x="${cx}" y="${cy+3}" text-anchor="middle" fill="#00ff41" font-size="8" font-weight="bold">${total>0?Math.round(pct*100):'-'}%</text>
</svg>
<div><div class="donut-lab">${won}W/${losses}L</div><div class="donut-lab">${total} total</div></div>
</div>`;
}
function momArrow(mom,score){
const dir=mom==='UP'?'':mom==='DOWN'?'':'';
const cls=mom==='UP'?'up':mom==='DOWN'?'dn':'skip';
return `<span class="${cls}">${dir} ${score.toFixed(3)}%</span>`;
}
async function load(){ async function load(){
const[fr,sr]=await Promise.all([fetch('/api/fleet'),fetch('/api/state')]); const[fr,sr]=await Promise.all([fetch('/api/fleet'),fetch('/api/state')]);
const d=await fr.json(),bs=await sr.json(); const d=await fr.json(),bs=await sr.json();
let h='',tp=0,to=0,tb=0,tw=0; // top metrics
let tp=0,to=0,tb=0,tw=0;
for(const v of Object.values(d)){tp+=v.pnl||0;to+=v.live_bets||0;tb+=v.total_bets||0;tw+=v.won||0}
document.getElementById('topbar').innerHTML=`
<div class="metric"><div class="val">$${(bs.balance/100).toFixed(2)}</div><div class="lab">BALANCE</div></div>
<div class="metric"><div class="val">${fmt(tp)}</div><div class="lab">TOTAL PNL</div></div>
<div class="metric"><div class="val">${to}</div><div class="lab">OPEN</div></div>
<div class="metric"><div class="val">${tw}W/${tb-tw}L</div><div class="lab">RECORD</div></div>
<div class="metric"><div class="val">${tb}</div><div class="lab">TOTAL BETS</div></div>
<div class="metric"><div class="val">${Object.keys(d).length}</div><div class="lab">COINS</div></div>`;
document.getElementById('clock').textContent=new Date().toLocaleTimeString();
// coin cards
let h='';
for(const[coin,v]of Object.entries(d)){ for(const[coin,v]of Object.entries(d)){
if(!v.mode||v.mode==='OFF')continue; if(!v.mode||v.mode==='OFF')continue;
tp+=v.pnl||0;to+=v.live_bets||0;tb+=v.total_bets||0;tw+=v.won||0; const wr=v.total_bets>0?(v.won||0)/v.total_bets:0;
const wr=v.total_bets>0?((v.won||0)/v.total_bets*100).toFixed(0):'-'; const chg=v.chg24||0;
// fetch tick history for sparkline
let ticks=[];
try{
const tr=await fetch(`/api/fleet/ticks?coin=${coin.toLowerCase()}&mins=60`);
const tj=await tr.json();
ticks=tj.prices||[];
}catch(e){}
h+=`<div class="card" style="border-color:${C[coin]}44"> h+=`<div class="card" style="border-color:${C[coin]}44">
<h2 style="color:${C[coin]}">${v.label} <span class="badge ${v.mode==='AUTO'?'up':'dn'}">${v.mode}</span></h2> <h2 style="color:${C[coin]}">${v.label} <span class="badge ${v.mode==='AUTO'?'up':'dn'}">${v.mode}</span></h2>
<div class="big">$${(v.price||0).toFixed(v.price<1?6:2)}</div> <div class="big">$${(v.price||0).toFixed(v.price<1?6:2)}</div>
<div class="row"><span>24h</span><span class="${(v.chg24||0)>=0?'up':'dn'}">${(v.chg24||0).toFixed(2)}%</span></div> <div class="chart-row">
<div class="row"><span>RSI</span><span>${v.rsi||'-'}</span></div> <span class="chart-label">60m</span>
<div class="row"><span>Momentum</span><span>${v.mom||'-'}</span></div> ${sparkline(ticks,C[coin],200,40)}
<div class="row"><span>WR</span><span>${wr}% (${v.won||0}W)</span></div> </div>
<div class="row"><span>Open</span><span>${v.live_bets||0}</span></div> <div class="row"><span>24h</span><span class="${chg>=0?'up':'dn'}">${chg.toFixed(2)}%</span></div>
<div class="row"><span>RSI</span>${gauge(v.rsi||0,C[coin])}</div>
<div class="row"><span>Momentum</span>${momArrow(v.mom||'FLAT',parseFloat((v.mom||'FLAT 0').split(' ')[1]||0))}</div>
<div class="row"><span>Win Rate</span>${donut(v.won||0,v.total_bets||0)}</div>
<div class="row"><span>Open</span><span>${v.live_bets||0} live</span></div>
<div class="row"><span>PNL</span><span>${fmt(v.pnl||0)}</span></div> <div class="row"><span>PNL</span><span>${fmt(v.pnl||0)}</span></div>
<div class="row"><span>Stake</span><span>${v.stake||10}¢</span></div> <div class="row"><span>Stake</span><span>${v.stake||10}¢</span></div>
${(v.decisions||[]).slice(0,2).map(dec=>` ${(v.decisions||[]).slice(0,3).map(dec=>`
<div class="dec"><span class="badge ${dec.final==='UP'?'up':dec.final==='DOWN'?'dn':'skip'}">${dec.final}</span> <div class="dec"><span class="badge ${dec.final==='UP'?'up':dec.final==='DOWN'?'dn':'skip'}">${dec.final}</span>
<span class="small">${ago(dec.ts)}</span> <span class="small">${ago(dec.ts)}</span>
<div class="llm">LLM: ${dec.llm_vote} conf=${dec.llm_conf}</div> <div class="llm">LLM: ${dec.llm_vote} conf=${dec.llm_conf}</div>
<div class="why">${(dec.reason||'').substring(0,110)}</div></div>`).join('')} <div class="why">${(dec.reason||'').substring(0,120)}</div></div>`).join('')}
</div>`; </div>`;
} }
document.getElementById('cards').innerHTML=h||'<div style="padding:20px">no coins</div>'; document.getElementById('cards').innerHTML=h||'<div style="padding:20px">no coins</div>';
document.getElementById('statusbar').innerHTML=`<div class="cell"><span class="lab">BAL</span><b>$${(bs.balance/100).toFixed(2)}</b></div><div class="cell"><span class="lab">PNL</span>${fmt(tp)}</div><div class="cell"><span class="lab">OPEN</span>${to}</div><div class="cell"><span class="lab">REC</span>${tw}W/${tb-tw}L</div>`; document.getElementById('statusbar').innerHTML=`<div class="cell"><span class="lab">BAL</span><b>$${(bs.balance/100).toFixed(2)}</b></div><div class="cell"><span class="lab">PNL</span>${fmt(tp)}</div><div class="cell"><span class="lab">OPEN</span>${to}</div><div class="cell"><span class="lab">REC</span>${tw}W/${tb-tw}L</div>`;
document.getElementById('tot').textContent=`PNL ${tp}¢`;
} }
load();setInterval(load,15000); load();setInterval(load,15000);
</script></body></html>""" </script></body></html>"""
@@ -390,6 +466,23 @@ def fleet_api():
return jsonify(result) return jsonify(result)
@app.route("/api/fleet/ticks")
def fleet_ticks():
"""Price tick history for sparkline charts."""
coin = request.args.get("coin", "doge").lower()
mins = int(request.args.get("mins", 60))
fpath = HOME / f"{coin}.json"
if not fpath.exists(): return jsonify({"prices": []})
c = json.loads(fpath.read_text())
db_path = HOME / c.get("db_path", f"{coin}.db")
if not db_path.exists(): return jsonify({"prices": []})
conn = sqlite3.connect(str(db_path))
rows = conn.execute("SELECT ts, price FROM ticks WHERE ts > ? ORDER BY ts",
(time.time() - mins*60,)).fetchall()
conn.close()
return jsonify({"prices": [r[1] for r in rows], "times": [r[0] for r in rows]})
@app.route("/fleet") @app.route("/fleet")
def fleet_page(): def fleet_page():
return FLEET_HTML return FLEET_HTML

BIN
doge.db

Binary file not shown.

BIN
eth.db

Binary file not shown.

BIN
sol.db

Binary file not shown.

BIN
xrp.db

Binary file not shown.