From 806c9e6c985c9b193d3c1ce3d7125c03fe561fdf Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 13:39:28 +0000 Subject: [PATCH] feat: enhance CLI UX with argument parsing and accessibility options - Added `argparse` to allow configuration of days, initial cash, initial price, and volatility. - Implemented `--quiet` flag to suppress daily output. - Implemented `--no-color` flag to disable ANSI color codes for accessibility. - Refactored simulation logic into a `main` function for better testability. - Added unit tests in `test_bitcoin_trading.py`. - Updated `.gitignore` to exclude Python cache files. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 +++ .gitignore | 5 +++ bitcoin_trading_simulation.py | 54 ++++++++++++++++++++++++----- test_bitcoin_trading.py | 64 +++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 test_bitcoin_trading.py diff --git a/.Jules/palette.md b/.Jules/palette.md index 5de3a38..5189b91 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -1,3 +1,7 @@ ## 2024-05-22 - Visual Hierarchy in CLI Output **Learning:** Adding color-coded indicators (Green/Red) and emojis (💰, 📉) in CLI tools significantly reduces cognitive load when parsing financial data streams. It transforms a wall of text into a scannable narrative. **Action:** For data-heavy CLI applications, always implement a semantic color system and visual anchors (icons/emojis) for key events. + +## 2024-05-23 - CLI Accessibility and Configuration +**Learning:** Hardcoded simulation parameters create friction and limit accessibility. Adding CLI arguments (`argparse`) empowers users to explore scenarios without code edits. Additionally, providing a `--no-color` flag is crucial for users with visual impairments or those piping output to logs. +**Action:** Always implement standard CLI flags for configuration and accessibility (quiet mode, no-color) in command-line tools. diff --git a/.gitignore b/.gitignore index d4fb281..03c45b7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,8 @@ # debug information files *.dwo + +# Python +__pycache__/ +*.py[cod] +*$py.class diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 82df43f..3557c3f 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,3 +1,4 @@ +import argparse import numpy as np import pandas as pd @@ -9,6 +10,15 @@ class Colors: ENDC = '\033[0m' BOLD = '\033[1m' + @classmethod + def disable(cls): + cls.HEADER = '' + cls.BLUE = '' + cls.GREEN = '' + cls.RED = '' + cls.ENDC = '' + cls.BOLD = '' + 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. @@ -49,7 +59,7 @@ def generate_trading_signals(signals): signals['positions'] = signals['signal'].diff().shift(1) return signals -def simulate_trading(signals, initial_cash=10000): +def simulate_trading(signals, initial_cash=10000, quiet=False): """ Simulates trading based on signals and prints a daily ledger. """ @@ -59,7 +69,8 @@ def simulate_trading(signals, initial_cash=10000): portfolio['btc'] = 0.0 portfolio['total_value'] = float(initial_cash) - print(f"{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}") + if not quiet: + print(f"{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}") for i, row in signals.iterrows(): if i > 0: portfolio.loc[i, 'cash'] = portfolio.loc[i-1, 'cash'] @@ -70,24 +81,30 @@ def simulate_trading(signals, initial_cash=10000): btc_to_buy = portfolio.loc[i, 'cash'] / row['price'] portfolio.loc[i, 'btc'] += btc_to_buy portfolio.loc[i, 'cash'] -= btc_to_buy * row['price'] - print(f"{Colors.GREEN}Day {i}: 💰 Buy {btc_to_buy:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}") + if not quiet: + print(f"{Colors.GREEN}Day {i}: 💰 Buy {btc_to_buy:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}") # Sell signal elif row['positions'] == -2.0: if portfolio.loc[i, 'btc'] > 0: cash_received = portfolio.loc[i, 'btc'] * row['price'] portfolio.loc[i, 'cash'] += cash_received - print(f"{Colors.RED}Day {i}: 📉 Sell {portfolio.loc[i, 'btc']:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}") + if not quiet: + print(f"{Colors.RED}Day {i}: 📉 Sell {portfolio.loc[i, 'btc']:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}") portfolio.loc[i, 'btc'] = 0 portfolio.loc[i, 'total_value'] = portfolio.loc[i, 'cash'] + portfolio.loc[i, 'btc'] * row['price'] - print(f"Day {i}: Portfolio Value: ${portfolio.loc[i, 'total_value']:.2f}, Cash: ${portfolio.loc[i, 'cash']:.2f}, BTC: {portfolio.loc[i, 'btc']:.4f}") + if not quiet: + print(f"Day {i}: Portfolio Value: ${portfolio.loc[i, 'total_value']:.2f}, Cash: ${portfolio.loc[i, 'cash']:.2f}, BTC: {portfolio.loc[i, 'btc']:.4f}") return portfolio -if __name__ == "__main__": +def main(days, initial_price, volatility, initial_cash, quiet, no_color): + if no_color: + Colors.disable() + # Simulate prices - prices = simulate_bitcoin_prices() + prices = simulate_bitcoin_prices(days=days, initial_price=initial_price, volatility=volatility) # Calculate moving averages signals = calculate_moving_averages(prices) @@ -96,11 +113,10 @@ def simulate_trading(signals, initial_cash=10000): signals = generate_trading_signals(signals) # Simulate trading - portfolio = simulate_trading(signals) + portfolio = simulate_trading(signals, initial_cash=initial_cash, quiet=quiet) # Final portfolio performance final_value = portfolio['total_value'].iloc[-1] - initial_cash = 10000 profit = final_value - initial_cash # Compare with buy and hold strategy @@ -118,3 +134,23 @@ def simulate_trading(signals, initial_cash=10000): print(f"Buy and Hold Strategy Value: ${buy_and_hold_value:.2f}") print(f"{Colors.HEADER}-----------------------------------------{Colors.ENDC}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Bitcoin Trading Simulation') + parser.add_argument('--days', type=int, default=60, help='Number of days to simulate') + parser.add_argument('--initial-cash', type=float, default=10000, help='Initial cash amount') + parser.add_argument('--initial-price', type=float, default=50000, help='Initial Bitcoin price') + parser.add_argument('--volatility', type=float, default=0.02, help='Volatility factor') + parser.add_argument('--quiet', action='store_true', help='Suppress daily output') + parser.add_argument('--no-color', action='store_true', help='Disable colored output') + + args = parser.parse_args() + + main( + days=args.days, + initial_price=args.initial_price, + volatility=args.volatility, + initial_cash=args.initial_cash, + quiet=args.quiet, + no_color=args.no_color + ) diff --git a/test_bitcoin_trading.py b/test_bitcoin_trading.py new file mode 100644 index 0000000..0d4c256 --- /dev/null +++ b/test_bitcoin_trading.py @@ -0,0 +1,64 @@ +import unittest +from unittest.mock import patch +import pandas as pd +from bitcoin_trading_simulation import Colors, simulate_trading, main, simulate_bitcoin_prices, calculate_moving_averages, generate_trading_signals + +class TestBitcoinSimulation(unittest.TestCase): + + def tearDown(self): + # Reset Colors to default just in case + Colors.HEADER = '\033[95m' + Colors.BLUE = '\033[94m' + Colors.GREEN = '\033[92m' + Colors.RED = '\033[91m' + Colors.ENDC = '\033[0m' + Colors.BOLD = '\033[1m' + + def test_colors_disable(self): + # Disable colors + Colors.disable() + + # Verify empty strings + self.assertEqual(Colors.HEADER, '') + self.assertEqual(Colors.BLUE, '') + self.assertEqual(Colors.GREEN, '') + self.assertEqual(Colors.RED, '') + self.assertEqual(Colors.ENDC, '') + self.assertEqual(Colors.BOLD, '') + + @patch('builtins.print') + def test_simulate_trading_quiet(self, mock_print): + # Create dummy signals + prices = pd.Series([100, 101, 102], name='Price') + signals = pd.DataFrame(index=prices.index) + signals['price'] = prices + signals['positions'] = 0.0 + + simulate_trading(signals, quiet=True) + + # Verify print was not called + mock_print.assert_not_called() + + @patch('builtins.print') + def test_simulate_trading_verbose(self, mock_print): + # Create dummy signals + prices = pd.Series([100, 101, 102], name='Price') + signals = pd.DataFrame(index=prices.index) + signals['price'] = prices + signals['positions'] = 0.0 + + simulate_trading(signals, quiet=False) + + # Verify print was called (at least for the header) + self.assertTrue(mock_print.called) + + def test_main_runs(self): + # Run main with small number of days to ensure no errors + # We can suppress output with quiet=True + try: + main(days=5, initial_price=100, volatility=0.01, initial_cash=1000, quiet=True, no_color=True) + except Exception as e: + self.fail(f"main() raised {e} unexpectedly!") + +if __name__ == '__main__': + unittest.main()