From 253a36d4219a7eda8bdacbb8218d7847c374c0c1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:25:45 +0000 Subject: [PATCH] feat(cli): Add input validation and progress bar - Add `validate_inputs` to `bitcoin_trading_simulation.py` to ensure CLI arguments are positive/valid. - Add text-based progress bar for `--quiet` mode simulations, guarded by `sys.stdout.isatty()` to prevent log pollution. - Improve error messages with ANSI colors for better visibility. - Fixes issue where negative inputs caused confusing "Day 0" output or crashes. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 ++ ...bitcoin_trading_simulation.cpython-312.pyc | Bin 10887 -> 13047 bytes bitcoin_trading_simulation.py | 38 +++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 96bd45d..c98d9d2 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -5,3 +5,7 @@ ## 2025-02-28 - Structured CLI Reports **Learning:** Dense numerical data in CLI output is hard to parse. Using ASCII box-drawing characters and alignment to create a "dashboard" or "invoice" style summary significantly improves readability and perceived quality. **Action:** When summarizing simulation or batch job results, always format the final report as a structured table or box rather than a list of print statements. + +## 2025-03-01 - CLI Progress Feedback +**Learning:** For long-running CLI processes (simulations), users need feedback. However, simply printing logs can be spammy. A text-based progress bar that only appears in interactive sessions (`sys.stdout.isatty()`) provides the best balance of feedback vs. cleanliness. +**Action:** Use `sys.stdout.write('\r...')` with `isatty()` checks for progress indicators in CLI tools. diff --git a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc index 81dd6550442bb097e35393c0939f196966f2f006..e5709ec93ebd0454fc54bb7aecdf43b257f1fab1 100644 GIT binary patch delta 5114 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 diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 001e336..86ec941 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,4 +1,5 @@ import argparse +import sys import numpy as np import pandas as pd @@ -27,6 +28,24 @@ def disable(cls): cls.UNDERLINE = '' +def validate_inputs(args): + """ + Validates CLI arguments. + """ + if args.days <= 0: + print(f"{Colors.FAIL}Error: --days must be a positive integer.{Colors.ENDC}") + sys.exit(1) + if args.initial_cash <= 0: + print(f"{Colors.FAIL}Error: --initial-cash must be positive.{Colors.ENDC}") + sys.exit(1) + if args.initial_price <= 0: + print(f"{Colors.FAIL}Error: --initial-price must be positive.{Colors.ENDC}") + sys.exit(1) + if args.volatility < 0: + print(f"{Colors.FAIL}Error: --volatility must be non-negative.{Colors.ENDC}") + sys.exit(1) + + def simulate_bitcoin_prices(days=60, initial_price=50000, volatility=0.02): """ Simulates Bitcoin prices for a given number of days using Geometric Brownian Motion. @@ -70,7 +89,7 @@ def generate_trading_signals(signals): return signals -def simulate_trading(signals, initial_cash=10000, quiet=False): +def simulate_trading(signals, initial_cash=10000, quiet=False, show_progress=False): """ Simulates trading based on signals and prints a daily ledger. """ @@ -79,10 +98,20 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): portfolio['cash'] = float(initial_cash) portfolio['btc'] = 0.0 portfolio['total_value'] = float(initial_cash) + total_days = len(signals) if not quiet: print(f"\n{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}") + for i, row in signals.iterrows(): + if show_progress and sys.stdout.isatty(): + progress = (i + 1) / total_days + bar_length = 40 + filled_length = int(bar_length * progress) + bar = '█' * filled_length + '-' * (bar_length - filled_length) + sys.stdout.write(f'\r{Colors.CYAN}Simulating: [{bar}] {progress:.1%}{Colors.ENDC}') + sys.stdout.flush() + if i > 0: portfolio.loc[i, 'cash'] = portfolio.loc[i-1, 'cash'] portfolio.loc[i, 'btc'] = portfolio.loc[i-1, 'btc'] @@ -108,6 +137,9 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): print(f"Day {i}: Portfolio Value: ${portfolio.loc[i, 'total_value']:.2f}, " f"Cash: ${portfolio.loc[i, 'cash']:.2f}, BTC: {portfolio.loc[i, 'btc']:.4f}") + if show_progress and sys.stdout.isatty(): + sys.stdout.write('\n') + return portfolio @@ -125,6 +157,8 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): if args.no_color: Colors.disable() + validate_inputs(args) + # Simulate prices prices = simulate_bitcoin_prices(days=args.days, initial_price=args.initial_price, volatility=args.volatility) @@ -135,7 +169,7 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): signals = generate_trading_signals(signals) # Simulate trading - portfolio = simulate_trading(signals, initial_cash=args.initial_cash, quiet=args.quiet) + portfolio = simulate_trading(signals, initial_cash=args.initial_cash, quiet=args.quiet, show_progress=args.quiet) # Final portfolio performance final_value = portfolio['total_value'].iloc[-1]