74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import pandas as pd
|
|
from futu import OpenQuoteContext, RET_OK, UserSecurityGroupType
|
|
|
|
|
|
def fetch_full_watchlist(host: str, port: int) -> pd.DataFrame:
|
|
quote_ctx = OpenQuoteContext(host=host, port=port)
|
|
try:
|
|
ret, groups = quote_ctx.get_user_security_group(group_type=UserSecurityGroupType.ALL)
|
|
if ret != RET_OK:
|
|
raise RuntimeError(f"get_user_security_group failed: {groups}")
|
|
|
|
all_frames: list[pd.DataFrame] = []
|
|
for _, g in groups.iterrows():
|
|
group_name = str(g.get("group_name", "")).strip()
|
|
if not group_name:
|
|
continue
|
|
ret2, sec = quote_ctx.get_user_security(group_name)
|
|
if ret2 != RET_OK:
|
|
print(f"[WARN] get_user_security failed for group={group_name}: {sec}")
|
|
continue
|
|
if sec is None or sec.empty:
|
|
continue
|
|
sec = sec.copy()
|
|
sec["group_name"] = group_name
|
|
all_frames.append(sec)
|
|
|
|
if not all_frames:
|
|
return pd.DataFrame(columns=["group_name", "code", "name"])
|
|
|
|
out = pd.concat(all_frames, ignore_index=True)
|
|
out = out.drop_duplicates(subset=["group_name", "code"], keep="first")
|
|
first_cols = ["group_name", "code", "name"]
|
|
ordered_cols = [c for c in first_cols if c in out.columns] + [c for c in out.columns if c not in first_cols]
|
|
return out[ordered_cols]
|
|
finally:
|
|
quote_ctx.close()
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Export all Futu watchlist groups to CSV/JSON.")
|
|
parser.add_argument("--host", default="127.0.0.1", help="OpenD host (default: 127.0.0.1)")
|
|
parser.add_argument("--port", type=int, default=11111, help="OpenD port (default: 11111)")
|
|
parser.add_argument("--csv", default="", help="Output CSV path")
|
|
parser.add_argument("--json", default="", help="Output JSON path")
|
|
args = parser.parse_args()
|
|
|
|
output_dir = Path(__file__).resolve().parent
|
|
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
csv_path = Path(args.csv) if args.csv else output_dir / f"futu_watchlist_full_{ts}.csv"
|
|
json_path = Path(args.json) if args.json else output_dir / f"futu_watchlist_full_{ts}.json"
|
|
|
|
df = fetch_full_watchlist(host=args.host, port=args.port)
|
|
df.to_csv(csv_path, index=False, encoding="utf-8-sig")
|
|
|
|
records = json.loads(df.to_json(orient="records", force_ascii=False))
|
|
json_path.write_text(json.dumps(records, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
print(f"Exported rows: {len(df)}")
|
|
print(f"CSV: {csv_path}")
|
|
print(f"JSON: {json_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|