dojozero-client 0.2.2__tar.gz → 0.4.0__tar.gz
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.
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/PKG-INFO +3 -3
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/README.md +1 -1
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/pyproject.toml +2 -2
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/__init__.py +11 -1
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_cli.py +246 -47
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_client.py +182 -10
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_daemon.py +314 -70
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_exceptions.py +22 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_transport.py +8 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/tests/test_client.py +3 -3
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/tests/test_daemon.py +216 -73
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/.gitignore +0 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_config.py +0 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_credentials.py +0 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_rpc.py +0 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/src/dojozero_client/_strategy.py +0 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/tests/test_config.py +0 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/tests/test_credentials.py +0 -0
- {dojozero_client-0.2.2 → dojozero_client-0.4.0}/tests/test_rpc.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dojozero-client
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Python SDK for external agents participating in DojoZero trials. Works with OpenClaw and
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Python SDK for external agents participating in DojoZero trials. Works with OpenClaw and QwenPaw.
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentscope-ai/DojoZero
|
|
6
6
|
Project-URL: Repository, https://github.com/agentscope-ai/DojoZero
|
|
7
7
|
Project-URL: Issues, https://github.com/agentscope-ai/DojoZero/issues
|
|
@@ -149,7 +149,7 @@ asyncio.run(main())
|
|
|
149
149
|
|
|
150
150
|
## Documentation
|
|
151
151
|
|
|
152
|
-
For the full API reference, daemon mode, multiple agent profiles, and Agent Skill integration (OpenClaw /
|
|
152
|
+
For the full API reference, daemon mode, multiple agent profiles, and Agent Skill integration (OpenClaw / QwenPaw / AgentScope), see the [full documentation](https://github.com/agentscope-ai/DojoZero/tree/main/docs).
|
|
153
153
|
|
|
154
154
|
## License
|
|
155
155
|
|
|
@@ -127,7 +127,7 @@ asyncio.run(main())
|
|
|
127
127
|
|
|
128
128
|
## Documentation
|
|
129
129
|
|
|
130
|
-
For the full API reference, daemon mode, multiple agent profiles, and Agent Skill integration (OpenClaw /
|
|
130
|
+
For the full API reference, daemon mode, multiple agent profiles, and Agent Skill integration (OpenClaw / QwenPaw / AgentScope), see the [full documentation](https://github.com/agentscope-ai/DojoZero/tree/main/docs).
|
|
131
131
|
|
|
132
132
|
## License
|
|
133
133
|
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dojozero-client"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Python SDK for external agents participating in DojoZero trials. Works with OpenClaw and
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "Python SDK for external agents participating in DojoZero trials. Works with OpenClaw and QwenPaw."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = {text = "MIT"}
|
|
@@ -22,11 +22,14 @@ from dojozero_client._client import (
|
|
|
22
22
|
AgentResult,
|
|
23
23
|
Balance,
|
|
24
24
|
BetResult,
|
|
25
|
+
ContestRules,
|
|
25
26
|
DojoClient,
|
|
26
27
|
EventEnvelope,
|
|
28
|
+
EventInfo,
|
|
27
29
|
GatewayInfo,
|
|
28
30
|
Holding,
|
|
29
31
|
Odds,
|
|
32
|
+
PredictionResult,
|
|
30
33
|
TrialConnection,
|
|
31
34
|
TrialEndedEvent,
|
|
32
35
|
TrialMetadata,
|
|
@@ -52,6 +55,8 @@ from dojozero_client._exceptions import (
|
|
|
52
55
|
DojoClientError,
|
|
53
56
|
InsufficientBalanceError,
|
|
54
57
|
NotRegisteredError,
|
|
58
|
+
PredictionClosedError,
|
|
59
|
+
PredictionRejectedError,
|
|
55
60
|
RateLimitedError,
|
|
56
61
|
RegistrationError,
|
|
57
62
|
StaleReferenceError,
|
|
@@ -60,7 +65,7 @@ from dojozero_client._exceptions import (
|
|
|
60
65
|
)
|
|
61
66
|
from dojozero_client._transport import GatewayTransport, SSEEvent
|
|
62
67
|
|
|
63
|
-
__version__ = "0.
|
|
68
|
+
__version__ = "0.4.0"
|
|
64
69
|
|
|
65
70
|
__all__ = [
|
|
66
71
|
# Main client
|
|
@@ -70,10 +75,13 @@ __all__ = [
|
|
|
70
75
|
"AgentResult",
|
|
71
76
|
"Balance",
|
|
72
77
|
"BetResult",
|
|
78
|
+
"ContestRules",
|
|
73
79
|
"EventEnvelope",
|
|
80
|
+
"EventInfo",
|
|
74
81
|
"GatewayInfo",
|
|
75
82
|
"Holding",
|
|
76
83
|
"Odds",
|
|
84
|
+
"PredictionResult",
|
|
77
85
|
"TrialEndedEvent",
|
|
78
86
|
"TrialMetadata",
|
|
79
87
|
"TrialResults",
|
|
@@ -100,6 +108,8 @@ __all__ = [
|
|
|
100
108
|
"StaleReferenceError",
|
|
101
109
|
"InsufficientBalanceError",
|
|
102
110
|
"BettingClosedError",
|
|
111
|
+
"PredictionRejectedError",
|
|
112
|
+
"PredictionClosedError",
|
|
103
113
|
"RateLimitedError",
|
|
104
114
|
"StreamDisconnectedError",
|
|
105
115
|
"TrialEndedError",
|
|
@@ -163,11 +163,10 @@ def cmd_start(args: argparse.Namespace) -> int:
|
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
async def _run_foreground() -> None:
|
|
166
|
-
daemon = UnifiedDaemon()
|
|
166
|
+
daemon = UnifiedDaemon(profile=profile)
|
|
167
167
|
# Start daemon, then auto-join the trial
|
|
168
168
|
daemon._stop_event = asyncio.Event()
|
|
169
|
-
daemon.
|
|
170
|
-
if not daemon._api_key:
|
|
169
|
+
if not daemon._get_api_key():
|
|
171
170
|
raise RuntimeError("No API key configured")
|
|
172
171
|
daemon._write_pid()
|
|
173
172
|
daemon._setup_signals()
|
|
@@ -229,10 +228,12 @@ def cmd_status(args: argparse.Namespace) -> int:
|
|
|
229
228
|
try:
|
|
230
229
|
state = client.call_sync("status", trial_id=trial_id)
|
|
231
230
|
_print_status(state, daemon_running=True)
|
|
232
|
-
# Show bet history from disk
|
|
233
231
|
tid = state.get("trial_id", trial_id or "")
|
|
234
232
|
if tid:
|
|
235
|
-
|
|
233
|
+
if state.get("contest_kind") == "window_pool_prediction":
|
|
234
|
+
_print_prediction_history(_trial_state_dir(tid))
|
|
235
|
+
else:
|
|
236
|
+
_print_bet_history(_trial_state_dir(tid))
|
|
236
237
|
return 0
|
|
237
238
|
except RPCError as e:
|
|
238
239
|
if e.code == "NO_TRIALS":
|
|
@@ -263,8 +264,12 @@ def _print_status(state: dict[str, Any], daemon_running: bool) -> None:
|
|
|
263
264
|
away_team = state.get("away_team", "")
|
|
264
265
|
home_tri = state.get("home_team_tricode", "")
|
|
265
266
|
away_tri = state.get("away_team_tricode", "")
|
|
267
|
+
contest_kind = state.get("contest_kind", "classic_betting")
|
|
268
|
+
is_prediction = contest_kind == "window_pool_prediction"
|
|
266
269
|
|
|
267
270
|
print(f"Trial: {state.get('trial_id', 'unknown')}")
|
|
271
|
+
mode_label = "prediction" if is_prediction else "classic betting"
|
|
272
|
+
print(f"Mode: {mode_label}")
|
|
268
273
|
if home_team and away_team:
|
|
269
274
|
sport = state.get("sport_type", "")
|
|
270
275
|
game_time = state.get("game_time", "")
|
|
@@ -283,11 +288,12 @@ def _print_status(state: dict[str, Any], daemon_running: bool) -> None:
|
|
|
283
288
|
else:
|
|
284
289
|
print(f"Status: {status_label} (daemon not running)")
|
|
285
290
|
|
|
286
|
-
# Use tricodes for compact score/odds, fall back to full name or "Home"/"Away"
|
|
287
291
|
home_label = home_tri or home_team or "Home"
|
|
288
292
|
away_label = away_tri or away_team or "Away"
|
|
289
293
|
|
|
290
|
-
|
|
294
|
+
def _print_game_state_score() -> None:
|
|
295
|
+
if not game_state:
|
|
296
|
+
return
|
|
291
297
|
home_score = game_state.get("home_score", "?")
|
|
292
298
|
away_score = game_state.get("away_score", "?")
|
|
293
299
|
period = game_state.get("period", game_state.get("quarter", "?"))
|
|
@@ -296,24 +302,51 @@ def _print_status(state: dict[str, Any], daemon_running: bool) -> None:
|
|
|
296
302
|
f"Score: {home_label} {home_score} - {away_label} {away_score} (Q{period} {clock})"
|
|
297
303
|
)
|
|
298
304
|
|
|
299
|
-
if
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
if is_prediction:
|
|
306
|
+
# Show event info for prediction mode
|
|
307
|
+
event_info = state.get("event_info", {})
|
|
308
|
+
if event_info:
|
|
309
|
+
window = event_info.get("current_window", "?")
|
|
310
|
+
ratio = event_info.get("elapsed_ratio", 0)
|
|
311
|
+
window_labels = {0: "Pre-game", 1: "Q1", 2: "Q2", 3: "Q3", 4: "Q4"}
|
|
312
|
+
wlabel = window_labels.get(window, f"W{window}")
|
|
313
|
+
print(f"Window: {wlabel} (elapsed {ratio:.0%})")
|
|
314
|
+
h_score = event_info.get("home_score")
|
|
315
|
+
a_score = event_info.get("away_score")
|
|
316
|
+
if h_score is not None and a_score is not None:
|
|
317
|
+
period = event_info.get("period", "?")
|
|
318
|
+
clock = event_info.get("game_clock", "")
|
|
319
|
+
time_str = f"Q{period} {clock}" if clock else f"Q{period}"
|
|
320
|
+
print(
|
|
321
|
+
f"Score: {home_label} {h_score} - {away_label} {a_score} ({time_str})"
|
|
322
|
+
)
|
|
323
|
+
else:
|
|
324
|
+
_print_game_state_score()
|
|
325
|
+
else:
|
|
326
|
+
# Classic betting mode
|
|
327
|
+
_print_game_state_score()
|
|
328
|
+
|
|
329
|
+
if odds:
|
|
330
|
+
home_prob = odds.get("home_probability", 0)
|
|
331
|
+
away_prob = odds.get("away_probability", 0)
|
|
307
332
|
print(
|
|
308
|
-
f"
|
|
333
|
+
f"Moneyline: {home_label} {home_prob:.1%}, {away_label} {away_prob:.1%}"
|
|
309
334
|
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
335
|
+
for s in odds.get("spreads", []):
|
|
336
|
+
spread_val = s.get("spread", 0)
|
|
337
|
+
h_p = s.get("home_probability", 0)
|
|
338
|
+
a_p = s.get("away_probability", 0)
|
|
339
|
+
print(
|
|
340
|
+
f"Spread {spread_val:+g}: {home_label} {h_p:.1%}, {away_label} {a_p:.1%}"
|
|
341
|
+
)
|
|
342
|
+
for t in odds.get("totals", []):
|
|
343
|
+
total_val = t.get("total", 0)
|
|
344
|
+
over_p = t.get("over_probability", 0)
|
|
345
|
+
under_p = t.get("under_probability", 0)
|
|
346
|
+
print(f"Total {total_val}: over {over_p:.1%}, under {under_p:.1%}")
|
|
347
|
+
|
|
348
|
+
print(f"Balance: ${state.get('balance', 0):.2f}")
|
|
315
349
|
|
|
316
|
-
print(f"Balance: ${state.get('balance', 0):.2f}")
|
|
317
350
|
print(f"Last Update: {state.get('last_updated', 'never')}")
|
|
318
351
|
|
|
319
352
|
|
|
@@ -323,7 +356,7 @@ def _print_bet_history(state_dir: Path) -> None:
|
|
|
323
356
|
if not bets_file.exists():
|
|
324
357
|
return
|
|
325
358
|
|
|
326
|
-
lines = bets_file.read_text().
|
|
359
|
+
lines = bets_file.read_text().splitlines()
|
|
327
360
|
bets = []
|
|
328
361
|
for line in lines:
|
|
329
362
|
if line:
|
|
@@ -344,6 +377,36 @@ def _print_bet_history(state_dir: Path) -> None:
|
|
|
344
377
|
print(f" ${amt:.0f} on {selection} ({market}){prob_str} - {ts}")
|
|
345
378
|
|
|
346
379
|
|
|
380
|
+
def _print_prediction_history(state_dir: Path) -> None:
|
|
381
|
+
"""Print prediction history from predictions.jsonl."""
|
|
382
|
+
preds_file = state_dir / "predictions.jsonl"
|
|
383
|
+
if not preds_file.exists():
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
lines = preds_file.read_text().splitlines()
|
|
387
|
+
preds = []
|
|
388
|
+
for line in lines:
|
|
389
|
+
if line:
|
|
390
|
+
try:
|
|
391
|
+
preds.append(json.loads(line))
|
|
392
|
+
except json.JSONDecodeError:
|
|
393
|
+
continue
|
|
394
|
+
if preds:
|
|
395
|
+
print(f"\nPredictions ({len(preds)}):")
|
|
396
|
+
for p in preds:
|
|
397
|
+
sel = p.get("selection", "?")
|
|
398
|
+
window = p.get("window", "?")
|
|
399
|
+
correct = p.get("is_correct")
|
|
400
|
+
score = p.get("score")
|
|
401
|
+
ts = p.get("submit_time", "")[:16]
|
|
402
|
+
status_str = ""
|
|
403
|
+
if correct is not None:
|
|
404
|
+
status_str = f" {'correct' if correct else 'incorrect'}"
|
|
405
|
+
if score is not None:
|
|
406
|
+
status_str += f" (score={score:.1f})"
|
|
407
|
+
print(f" W{window} {sel}{status_str} - {ts}")
|
|
408
|
+
|
|
409
|
+
|
|
347
410
|
def cmd_logs(args: argparse.Namespace) -> int:
|
|
348
411
|
"""Show daemon logs."""
|
|
349
412
|
state_dir = _get_state_dir(args)
|
|
@@ -361,7 +424,7 @@ def cmd_logs(args: argparse.Namespace) -> int:
|
|
|
361
424
|
pass
|
|
362
425
|
else:
|
|
363
426
|
# Show last 50 lines
|
|
364
|
-
lines = log_file.read_text().
|
|
427
|
+
lines = log_file.read_text().splitlines()
|
|
365
428
|
for line in lines[-50:]:
|
|
366
429
|
print(line)
|
|
367
430
|
|
|
@@ -405,6 +468,95 @@ def cmd_bet(args: argparse.Namespace) -> int:
|
|
|
405
468
|
return 1
|
|
406
469
|
|
|
407
470
|
|
|
471
|
+
def cmd_predict(args: argparse.Namespace) -> int:
|
|
472
|
+
"""Submit a prediction via daemon RPC."""
|
|
473
|
+
trial_id = getattr(args, "trial_id", None)
|
|
474
|
+
|
|
475
|
+
if not is_daemon_running():
|
|
476
|
+
print("Daemon not running. Use 'start <trial-id>' first.", file=sys.stderr)
|
|
477
|
+
return 1
|
|
478
|
+
|
|
479
|
+
client = RPCClient(SOCKET_PATH)
|
|
480
|
+
try:
|
|
481
|
+
result = client.call_sync(
|
|
482
|
+
"predict",
|
|
483
|
+
trial_id=trial_id,
|
|
484
|
+
selection=args.selection,
|
|
485
|
+
)
|
|
486
|
+
window = result.get("window", "?")
|
|
487
|
+
print(f"Prediction submitted: {args.selection} (window {window})")
|
|
488
|
+
print(f"Prediction ID: {result.get('prediction_id')}")
|
|
489
|
+
return 0
|
|
490
|
+
except RPCError as e:
|
|
491
|
+
print(f"Error: {e.message}", file=sys.stderr)
|
|
492
|
+
return 1
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _format_prediction_row(p: dict[str, Any]) -> str:
|
|
496
|
+
"""Render a single prediction-history row.
|
|
497
|
+
|
|
498
|
+
Used by both the live-RPC and on-disk fallback paths so the two stay in
|
|
499
|
+
sync. Scores come back as Decimal-strings from the server and as floats
|
|
500
|
+
or strings from predictions.jsonl, so we accept either.
|
|
501
|
+
"""
|
|
502
|
+
pid = p.get("prediction_id", "?")[:8]
|
|
503
|
+
sel = p.get("selection", "?")
|
|
504
|
+
window = p.get("window", "?")
|
|
505
|
+
correct = p.get("is_correct")
|
|
506
|
+
score = p.get("score")
|
|
507
|
+
status_str = ""
|
|
508
|
+
if correct is not None:
|
|
509
|
+
status_str = f" {'✓' if correct else '✗'}"
|
|
510
|
+
if score is not None:
|
|
511
|
+
try:
|
|
512
|
+
status_str += f" score={float(score):.1f}"
|
|
513
|
+
except (TypeError, ValueError):
|
|
514
|
+
status_str += f" score={score}"
|
|
515
|
+
return f" [{pid}] W{window} {sel}{status_str}"
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def cmd_predictions(args: argparse.Namespace) -> int:
|
|
519
|
+
"""Show prediction history."""
|
|
520
|
+
trial_id = getattr(args, "trial_id", None)
|
|
521
|
+
|
|
522
|
+
# Try RPC first for live data
|
|
523
|
+
if is_daemon_running():
|
|
524
|
+
client = RPCClient(SOCKET_PATH)
|
|
525
|
+
try:
|
|
526
|
+
result = client.call_sync("predictions", trial_id=trial_id)
|
|
527
|
+
preds = result.get("predictions", [])
|
|
528
|
+
if not preds:
|
|
529
|
+
print("No predictions")
|
|
530
|
+
return 0
|
|
531
|
+
|
|
532
|
+
print(f"Predictions ({len(preds)}):")
|
|
533
|
+
for p in preds:
|
|
534
|
+
print(_format_prediction_row(p))
|
|
535
|
+
return 0
|
|
536
|
+
except RPCError as e:
|
|
537
|
+
print(f"Error: {e.message}", file=sys.stderr)
|
|
538
|
+
return 1
|
|
539
|
+
|
|
540
|
+
# Fall back to predictions.jsonl on disk
|
|
541
|
+
state_dir = _get_state_dir(args)
|
|
542
|
+
preds_file = state_dir / "predictions.jsonl"
|
|
543
|
+
if not preds_file.exists():
|
|
544
|
+
print("No predictions")
|
|
545
|
+
return 0
|
|
546
|
+
|
|
547
|
+
lines = preds_file.read_text().splitlines()
|
|
548
|
+
count = args.count if hasattr(args, "count") and args.count else 20
|
|
549
|
+
for line in lines[-count:]:
|
|
550
|
+
if not line:
|
|
551
|
+
continue
|
|
552
|
+
try:
|
|
553
|
+
p = json.loads(line)
|
|
554
|
+
except json.JSONDecodeError:
|
|
555
|
+
continue
|
|
556
|
+
print(_format_prediction_row(p))
|
|
557
|
+
return 0
|
|
558
|
+
|
|
559
|
+
|
|
408
560
|
def _format_event_summary(payload: dict[str, Any], home: str, away: str) -> str:
|
|
409
561
|
"""Format an event payload as a human-readable summary line."""
|
|
410
562
|
event_type = payload.get("event_type", "")
|
|
@@ -477,7 +629,7 @@ def cmd_events(args: argparse.Namespace) -> int:
|
|
|
477
629
|
for t in raw_types.split(",")
|
|
478
630
|
}
|
|
479
631
|
|
|
480
|
-
lines = events_file.read_text().
|
|
632
|
+
lines = events_file.read_text().splitlines()
|
|
481
633
|
count = args.count if hasattr(args, "count") and args.count else 20
|
|
482
634
|
|
|
483
635
|
# First pass: discover team tricodes from odds/result events
|
|
@@ -549,7 +701,7 @@ def cmd_bets(args: argparse.Namespace) -> int:
|
|
|
549
701
|
print("No bets")
|
|
550
702
|
return 0
|
|
551
703
|
|
|
552
|
-
lines = bets_file.read_text().
|
|
704
|
+
lines = bets_file.read_text().splitlines()
|
|
553
705
|
count = args.count if hasattr(args, "count") and args.count else 20
|
|
554
706
|
|
|
555
707
|
for line in lines[-count:]:
|
|
@@ -587,8 +739,12 @@ def cmd_list(_: argparse.Namespace) -> int:
|
|
|
587
739
|
print(f"Connected trials ({len(trials)}):")
|
|
588
740
|
for trial_id, info in trials.items():
|
|
589
741
|
status = "connected" if info.get("connected") else "disconnected"
|
|
590
|
-
|
|
591
|
-
|
|
742
|
+
kind = info.get("contest_kind", "classic_betting")
|
|
743
|
+
if kind == "window_pool_prediction":
|
|
744
|
+
print(f" {trial_id}: {status} (prediction mode)")
|
|
745
|
+
else:
|
|
746
|
+
balance = info.get("balance", 0)
|
|
747
|
+
print(f" {trial_id}: {status}, balance=${balance:.2f}")
|
|
592
748
|
return 0
|
|
593
749
|
except RPCError as e:
|
|
594
750
|
print(f"Error: {e.message}", file=sys.stderr)
|
|
@@ -627,6 +783,7 @@ def cmd_leaderboard(args: argparse.Namespace) -> int:
|
|
|
627
783
|
|
|
628
784
|
data = resp.json()
|
|
629
785
|
board = data.get("leaderboard", [])
|
|
786
|
+
mode = data.get("mode", "classic_betting")
|
|
630
787
|
|
|
631
788
|
if not board:
|
|
632
789
|
print("No agents registered")
|
|
@@ -645,27 +802,49 @@ def cmd_leaderboard(args: argparse.Namespace) -> int:
|
|
|
645
802
|
f"{data.get('internal_agents', 0)} internal)"
|
|
646
803
|
)
|
|
647
804
|
print()
|
|
648
|
-
print(
|
|
649
|
-
f" {'#':<4} {'Agent':<24} {'Balance':>10} {'P/L':>10} {'Bets':>6} {'Win%':>6} {'ROI':>7}"
|
|
650
|
-
)
|
|
651
|
-
print(
|
|
652
|
-
f" {'─' * 4} {'─' * 24} {'─' * 10} {'─' * 10} {'─' * 6} {'─' * 6} {'─' * 7}"
|
|
653
|
-
)
|
|
654
805
|
|
|
655
|
-
|
|
656
|
-
agent = entry["agent_id"]
|
|
657
|
-
if len(agent) > 23:
|
|
658
|
-
agent = agent[:20] + "..."
|
|
659
|
-
tag = " *" if entry.get("is_external") else ""
|
|
660
|
-
balance = float(entry.get("balance", 0))
|
|
661
|
-
pnl = float(entry.get("net_profit", 0))
|
|
662
|
-
bets = entry.get("total_bets", 0)
|
|
663
|
-
wr = entry.get("win_rate", 0)
|
|
664
|
-
roi = entry.get("roi", 0)
|
|
806
|
+
if mode == "prediction":
|
|
665
807
|
print(
|
|
666
|
-
f" {
|
|
667
|
-
f"{'
|
|
808
|
+
f" {'#':<4} {'Agent':<24} {'Score':>10} {'Preds':>6} "
|
|
809
|
+
f"{'Correct':>8} {'Accuracy':>9}"
|
|
668
810
|
)
|
|
811
|
+
print(f" {'─' * 4} {'─' * 24} {'─' * 10} {'─' * 6} {'─' * 8} {'─' * 9}")
|
|
812
|
+
for i, entry in enumerate(board, 1):
|
|
813
|
+
agent = entry["agent_id"]
|
|
814
|
+
if len(agent) > 23:
|
|
815
|
+
agent = agent[:20] + "..."
|
|
816
|
+
tag = " *" if entry.get("is_external") else ""
|
|
817
|
+
score = float(entry.get("total_score", 0))
|
|
818
|
+
total = entry.get("total_predictions", 0)
|
|
819
|
+
correct = entry.get("correct_predictions", 0)
|
|
820
|
+
accuracy = entry.get("accuracy", 0)
|
|
821
|
+
print(
|
|
822
|
+
f" {i:<4} {agent + tag:<24} {score:>10.1f} "
|
|
823
|
+
f"{total:>6} {correct:>8} {accuracy:>8.0%}"
|
|
824
|
+
)
|
|
825
|
+
else:
|
|
826
|
+
print(
|
|
827
|
+
f" {'#':<4} {'Agent':<24} {'Balance':>10} {'P/L':>10} "
|
|
828
|
+
f"{'Bets':>6} {'Win%':>6} {'ROI':>7}"
|
|
829
|
+
)
|
|
830
|
+
print(
|
|
831
|
+
f" {'─' * 4} {'─' * 24} {'─' * 10} {'─' * 10} "
|
|
832
|
+
f"{'─' * 6} {'─' * 6} {'─' * 7}"
|
|
833
|
+
)
|
|
834
|
+
for i, entry in enumerate(board, 1):
|
|
835
|
+
agent = entry["agent_id"]
|
|
836
|
+
if len(agent) > 23:
|
|
837
|
+
agent = agent[:20] + "..."
|
|
838
|
+
tag = " *" if entry.get("is_external") else ""
|
|
839
|
+
balance = float(entry.get("balance", 0))
|
|
840
|
+
pnl = float(entry.get("net_profit", 0))
|
|
841
|
+
bets = entry.get("total_bets", 0)
|
|
842
|
+
wr = entry.get("win_rate", 0)
|
|
843
|
+
roi = entry.get("roi", 0)
|
|
844
|
+
print(
|
|
845
|
+
f" {i:<4} {agent + tag:<24} ${balance:>9.2f} "
|
|
846
|
+
f"{'+' if pnl >= 0 else ''}{pnl:>9.2f} {bets:>6} {wr:>5.0%} {roi:>6.0%}"
|
|
847
|
+
)
|
|
669
848
|
|
|
670
849
|
print()
|
|
671
850
|
print(" * = external agent")
|
|
@@ -851,7 +1030,7 @@ def cmd_daemon_start(args: argparse.Namespace) -> int:
|
|
|
851
1030
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
852
1031
|
)
|
|
853
1032
|
|
|
854
|
-
daemon = UnifiedDaemon()
|
|
1033
|
+
daemon = UnifiedDaemon(profile=profile)
|
|
855
1034
|
try:
|
|
856
1035
|
asyncio.run(daemon.start())
|
|
857
1036
|
except KeyboardInterrupt:
|
|
@@ -1120,6 +1299,26 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
1120
1299
|
)
|
|
1121
1300
|
p_bet.set_defaults(func=cmd_bet)
|
|
1122
1301
|
|
|
1302
|
+
# predict (prediction mode)
|
|
1303
|
+
p_predict = subparsers.add_parser("predict", help="Submit a prediction")
|
|
1304
|
+
p_predict.add_argument(
|
|
1305
|
+
"trial_id", nargs="?", help="Trial ID (optional if only one running)"
|
|
1306
|
+
)
|
|
1307
|
+
p_predict.add_argument(
|
|
1308
|
+
"selection",
|
|
1309
|
+
choices=["home_win", "away_win", "even"],
|
|
1310
|
+
help="Prediction selection",
|
|
1311
|
+
)
|
|
1312
|
+
p_predict.set_defaults(func=cmd_predict)
|
|
1313
|
+
|
|
1314
|
+
# predictions (prediction mode)
|
|
1315
|
+
p_preds = subparsers.add_parser("predictions", help="Show prediction history")
|
|
1316
|
+
p_preds.add_argument(
|
|
1317
|
+
"trial_id", nargs="?", help="Trial ID (optional if only one running)"
|
|
1318
|
+
)
|
|
1319
|
+
p_preds.add_argument("-n", "--count", type=int, default=20, help="Number to show")
|
|
1320
|
+
p_preds.set_defaults(func=cmd_predictions)
|
|
1321
|
+
|
|
1123
1322
|
# events
|
|
1124
1323
|
p_events = subparsers.add_parser("events", help="Show event log")
|
|
1125
1324
|
p_events.add_argument(
|