Initial commit
This commit is contained in:
206
skills/kalshi-markets/scripts/series_list.py
Normal file
206
skills/kalshi-markets/scripts/series_list.py
Normal file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "httpx",
|
||||
# "click",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
"""
|
||||
Kalshi Series List Script
|
||||
|
||||
List all available series (market templates).
|
||||
Completely self-contained with embedded HTTP client.
|
||||
|
||||
Usage:
|
||||
uv run series_list.py
|
||||
uv run series_list.py --category Politics
|
||||
uv run series_list.py --json
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
import httpx
|
||||
|
||||
# Configuration
|
||||
API_BASE_URL = "https://api.elections.kalshi.com/trade-api/v2"
|
||||
API_TIMEOUT = 60.0 # Longer timeout for large series list
|
||||
USER_AGENT = "Kalshi-CLI/1.0"
|
||||
|
||||
|
||||
class KalshiClient:
|
||||
"""Minimal HTTP client for Kalshi API - series list functionality"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize HTTP client"""
|
||||
self.client = httpx.Client(
|
||||
base_url=API_BASE_URL, timeout=API_TIMEOUT, headers={"User-Agent": USER_AGENT}
|
||||
)
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry"""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Context manager exit - cleanup"""
|
||||
self.client.close()
|
||||
|
||||
def get_series_list(
|
||||
self, category: str | None = None, tags: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Get list of all series.
|
||||
|
||||
Args:
|
||||
category: Filter by category
|
||||
tags: Filter by tags (comma-separated)
|
||||
|
||||
Returns:
|
||||
Dict with 'series' array
|
||||
|
||||
Raises:
|
||||
Exception if API call fails
|
||||
"""
|
||||
params = {}
|
||||
if category:
|
||||
params["category"] = category
|
||||
if tags:
|
||||
params["tags"] = tags
|
||||
|
||||
try:
|
||||
response = self.client.get("/series", params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise Exception(f"API error: {e.response.status_code} - {e.response.text}")
|
||||
except httpx.RequestError as e:
|
||||
raise Exception(f"Network error: {str(e)}")
|
||||
except Exception as e:
|
||||
raise Exception(f"Unexpected error: {str(e)}")
|
||||
|
||||
|
||||
def format_series_summary(series: dict[str, Any]) -> str:
|
||||
"""Format a single series for display"""
|
||||
ticker = series.get("ticker", "N/A")
|
||||
title = series.get("title", "N/A")
|
||||
category = series.get("category", "N/A")
|
||||
frequency = series.get("frequency", "N/A")
|
||||
tags = series.get("tags", [])
|
||||
|
||||
lines = []
|
||||
lines.append(f"📈 {ticker}")
|
||||
lines.append(f" {title[:60]}{'...' if len(title) > 60 else ''}")
|
||||
lines.append(f" Category: {category} | Frequency: {frequency}")
|
||||
|
||||
if tags:
|
||||
tags_str = ", ".join(tags[:3])
|
||||
if len(tags) > 3:
|
||||
tags_str += f" (+{len(tags)-3} more)"
|
||||
lines.append(f" Tags: {tags_str}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def format_series_list(data: dict[str, Any], limit: int = 50) -> str:
|
||||
"""
|
||||
Format series list for human-readable output.
|
||||
|
||||
Args:
|
||||
data: Response data with series array
|
||||
limit: Number of series to display (rest shown as count)
|
||||
|
||||
Returns:
|
||||
Formatted string for display
|
||||
"""
|
||||
series_list = data.get("series", [])
|
||||
total_count = len(series_list)
|
||||
|
||||
lines = []
|
||||
lines.append("\n" + "=" * 60)
|
||||
lines.append("📚 Kalshi Series (Market Templates)")
|
||||
lines.append("=" * 60)
|
||||
lines.append(f"Total: {total_count} series available\n")
|
||||
|
||||
# Group by category
|
||||
categories = {}
|
||||
for series in series_list:
|
||||
cat = series.get("category", "Uncategorized")
|
||||
if cat not in categories:
|
||||
categories[cat] = []
|
||||
categories[cat].append(series)
|
||||
|
||||
# Show summary by category
|
||||
lines.append("📂 By Category:")
|
||||
lines.append("─" * 40)
|
||||
for cat in sorted(categories.keys()):
|
||||
count = len(categories[cat])
|
||||
lines.append(f" {cat}: {count} series")
|
||||
lines.append("")
|
||||
|
||||
# Show first N series
|
||||
display_count = min(limit, total_count)
|
||||
lines.append(f"📋 First {display_count} Series:")
|
||||
lines.append("─" * 60)
|
||||
|
||||
for i, series in enumerate(series_list[:display_count], 1):
|
||||
lines.append(f"{i}. {format_series_summary(series)}")
|
||||
lines.append("")
|
||||
|
||||
if total_count > display_count:
|
||||
lines.append(f"... and {total_count - display_count} more series")
|
||||
lines.append("\nTip: Use --json to get full list for processing")
|
||||
|
||||
lines.append("=" * 60)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--category", help="Filter by category (e.g., Politics, Economics)")
|
||||
@click.option("--tags", help="Filter by tags (comma-separated)")
|
||||
@click.option(
|
||||
"--limit", default=50, type=int, help="Number of series to display in human-readable mode"
|
||||
)
|
||||
@click.option(
|
||||
"--json", "output_json", is_flag=True, help="Output as JSON instead of human-readable format"
|
||||
)
|
||||
def main(category: str | None, tags: str | None, limit: int, output_json: bool):
|
||||
"""
|
||||
List all available series (market templates).
|
||||
|
||||
Series are templates for creating markets. There are ~6900 series available.
|
||||
No authentication required.
|
||||
|
||||
Note: This returns a large dataset. Use filters to narrow results.
|
||||
"""
|
||||
try:
|
||||
# Get series list from API
|
||||
with KalshiClient() as client:
|
||||
data = client.get_series_list(category=category, tags=tags)
|
||||
|
||||
# Output results
|
||||
if output_json:
|
||||
# JSON output for automation/MCP
|
||||
click.echo(json.dumps(data, indent=2))
|
||||
else:
|
||||
# Human-readable output (limited display)
|
||||
formatted = format_series_list(data, limit=limit)
|
||||
click.echo(formatted)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
if output_json:
|
||||
# JSON error format
|
||||
error_data = {"error": str(e)}
|
||||
click.echo(json.dumps(error_data, indent=2))
|
||||
else:
|
||||
# Human-readable error
|
||||
click.echo(f"❌ Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user