This commit is contained in:
2026-08-23 10:45:28 +08:00
commit dacfb9262b
18 changed files with 2752 additions and 0 deletions

4
cbpred/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
"""Unified 8H drawdown prediction pipeline."""
__version__ = "0.1.0"

138
cbpred/__main__.py Normal file
View File

@@ -0,0 +1,138 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from .pipeline import (
VOLATILITY_RANK_THRESHOLD,
parse_time_ms,
predict_from_csv,
prepare_data,
run_training,
)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Train a unified 8H futures drawdown model")
sub = parser.add_subparsers(dest="command", required=True)
prepare = sub.add_parser("prepare", help="filter symbols, call banbot, export complete 8H CSVs")
prepare.add_argument("--out-dir", type=Path, default=Path("data"))
prepare.add_argument("--banbot-dir", type=Path, default=Path("/home/ctyun/ban/banbot"))
prepare.add_argument("--banbot-config", type=Path, default=Path("/home/ctyun/ban/banbot/biz/config.yml"))
prepare.add_argument("--dsn", default=None)
prepare.add_argument("--db-type", choices=["auto", "questdb", "timescale"], default="auto")
prepare.add_argument("--exchange", default="binance")
prepare.add_argument("--end", dest="end_ms", default=None, help="UTC date/ISO timestamp or milliseconds")
prepare.add_argument("--history-months", type=int, default=36)
prepare.add_argument("--holdout-months", type=int, default=10)
prepare.add_argument("--min-age-days", type=int, default=730)
prepare.add_argument("--chunk-size", type=int, default=100)
prepare.add_argument("--banbot-cmd", default="go run .")
prepare.add_argument("--banbot-export-cmd", default="go run ./cmd/cbpred-export")
prepare.add_argument("--skip-download", action="store_true")
train = sub.add_parser("train", help="build three feature stages, tune on validation symbols, test once")
train.add_argument("--data-dir", type=Path, default=Path("data"))
train.add_argument("--artifact-dir", type=Path, default=Path("artifacts_signed"))
train.add_argument("--end", dest="end_ms", default=None)
train.add_argument("--test-months", type=int, default=10)
train.add_argument("--seed", type=int, default=42)
train.add_argument("--n-estimators", type=int, default=250)
train.add_argument("--n-jobs", type=int, default=-1)
train.add_argument("--volatility-rank-threshold", type=float, default=VOLATILITY_RANK_THRESHOLD)
run = sub.add_parser("run", help="prepare data and train the model")
for action in prepare._actions[1:]:
if action.dest not in {"help", "command"}:
run._add_action(action)
run.add_argument("--artifact-dir", type=Path, default=Path("artifacts_signed"))
run.add_argument("--test-months", type=int, default=10)
run.add_argument("--seed", type=int, default=42)
run.add_argument("--n-estimators", type=int, default=250)
run.add_argument("--n-jobs", type=int, default=-1)
run.add_argument("--volatility-rank-threshold", type=float, default=VOLATILITY_RANK_THRESHOLD)
predict = sub.add_parser(
"predict", help="predict signed robust future-low q20/q80 from an exported 8H CSV"
)
predict.add_argument("--artifact", type=Path, default=Path("artifacts_signed/model.joblib"))
predict.add_argument("--csv", type=Path, required=True)
predict.add_argument("--at", dest="at_ms", default=None)
predict.add_argument("--symbol", default=None, help="symbol used for the fixed training asset profile")
return parser
def main() -> None:
args = build_parser().parse_args()
if args.command == "prepare":
result = prepare_data(
out_dir=args.out_dir,
banbot_dir=args.banbot_dir,
banbot_config=args.banbot_config,
dsn=args.dsn,
db_type=args.db_type,
exchange=args.exchange,
end_ms=parse_time_ms(args.end_ms) if args.end_ms else None,
history_months=args.history_months,
holdout_months=args.holdout_months,
min_age_days=args.min_age_days,
chunk_size=args.chunk_size,
skip_download=args.skip_download,
banbot_cmd=args.banbot_cmd,
banbot_export_cmd=args.banbot_export_cmd,
)
print(json.dumps({"selected": len(result["symbols"]), "excluded": len(result["excluded"])}, indent=2))
elif args.command == "train":
result = run_training(
data_dir=args.data_dir,
artifact_dir=args.artifact_dir,
end_ms=parse_time_ms(args.end_ms) if args.end_ms else None,
test_months=args.test_months,
seed=args.seed,
n_estimators=args.n_estimators,
n_jobs=args.n_jobs,
volatility_rank_threshold=args.volatility_rank_threshold,
)
print(json.dumps(result, indent=2, ensure_ascii=False))
elif args.command == "run":
prepare_data(
out_dir=args.out_dir,
banbot_dir=args.banbot_dir,
banbot_config=args.banbot_config,
dsn=args.dsn,
db_type=args.db_type,
exchange=args.exchange,
end_ms=parse_time_ms(args.end_ms) if args.end_ms else None,
history_months=args.history_months,
holdout_months=args.holdout_months,
min_age_days=args.min_age_days,
chunk_size=args.chunk_size,
skip_download=args.skip_download,
banbot_cmd=args.banbot_cmd,
banbot_export_cmd=args.banbot_export_cmd,
)
result = run_training(
data_dir=args.out_dir,
artifact_dir=args.artifact_dir,
end_ms=parse_time_ms(args.end_ms) if args.end_ms else None,
test_months=args.test_months,
seed=args.seed,
n_estimators=args.n_estimators,
n_jobs=args.n_jobs,
volatility_rank_threshold=args.volatility_rank_threshold,
)
print(json.dumps(result, indent=2, ensure_ascii=False))
else:
result = predict_from_csv(
artifact_path=args.artifact,
csv_path=args.csv,
at_ms=parse_time_ms(args.at_ms) if args.at_ms else None,
symbol=args.symbol,
)
print(json.dumps(result, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()

1637
cbpred/pipeline.py Normal file

File diff suppressed because it is too large Load Diff