From 38517459a7298445047687942ef08f903fb75671 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:11:00 +0000 Subject: [PATCH] feat: Add CLI arguments for configuration and accessibility - Implemented `argparse` in `bitcoin_trading_simulation.py` to support `--days`, `--initial-cash`, `--initial-price`, `--volatility`, `--quiet`, and `--no-color`. - Updated `Colors` class to include a `disable` method for accessibility. - Updated `simulate_trading` to respect `--quiet` flag. - Added `test_bitcoin_trading.py` with unit tests for simulation logic and CLI flags. - Updated `.gitignore` to exclude Python artifacts (`__pycache__`, `*.pyc`). - Updated `.Jules/palette.md` with CLI accessibility learning. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 ++ .gitignore | 4 ++ bitcoin_trading_simulation.py | 43 ++++++++++++++++++---- test_bitcoin_trading.py | 69 +++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 test_bitcoin_trading.py diff --git a/.Jules/palette.md b/.Jules/palette.md index 5de3a38..1aab157 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:** While color-coding is great, it must be optional. Adding a `--no-color` flag is crucial for accessibility (e.g., color blindness, strict terminal environments). Similarly, a `--quiet` flag respects the user's need for concise output in automated pipelines. +**Action:** Always include `--no-color` and `--quiet` flags in CLI tools to support diverse user needs and environments. diff --git a/.gitignore b/.gitignore index d4fb281..907591b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ # debug information files *.dwo + +# Python +__pycache__/ +*.pyc diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 82df43f..3101a8e 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,40 @@ 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__": + parser = argparse.ArgumentParser(description="Bitcoin Trading Simulation") + parser.add_argument("--days", type=int, default=60, help="Number of days to simulate (default: 60)") + parser.add_argument("--initial-cash", type=float, default=10000, help="Initial cash amount (default: 10000)") + parser.add_argument("--initial-price", type=float, default=50000, help="Initial Bitcoin price (default: 50000)") + parser.add_argument("--volatility", type=float, default=0.02, help="Volatility factor (default: 0.02)") + parser.add_argument("--quiet", action="store_true", help="Suppress daily output") + parser.add_argument("--no-color", action="store_true", help="Disable color output") + + args = parser.parse_args() + + if args.no_color: + Colors.disable() + # Simulate prices - prices = simulate_bitcoin_prices() + prices = simulate_bitcoin_prices(days=args.days, initial_price=args.initial_price, volatility=args.volatility) # Calculate moving averages signals = calculate_moving_averages(prices) @@ -96,11 +123,11 @@ def simulate_trading(signals, initial_cash=10000): signals = generate_trading_signals(signals) # Simulate trading - portfolio = simulate_trading(signals) + portfolio = simulate_trading(signals, initial_cash=args.initial_cash, quiet=args.quiet) # Final portfolio performance final_value = portfolio['total_value'].iloc[-1] - initial_cash = 10000 + initial_cash = args.initial_cash profit = final_value - initial_cash # Compare with buy and hold strategy diff --git a/test_bitcoin_trading.py b/test_bitcoin_trading.py new file mode 100644 index 0000000..c062536 --- /dev/null +++ b/test_bitcoin_trading.py @@ -0,0 +1,69 @@ +import unittest +import pandas as pd +import numpy as np +from bitcoin_trading_simulation import ( + simulate_bitcoin_prices, + calculate_moving_averages, + generate_trading_signals, + Colors +) + +class TestBitcoinTrading(unittest.TestCase): + def test_simulate_bitcoin_prices(self): + days = 10 + initial_price = 100 + prices = simulate_bitcoin_prices(days=days, initial_price=initial_price) + self.assertIsInstance(prices, pd.Series) + self.assertEqual(len(prices), days) + self.assertEqual(prices.iloc[0], initial_price) + + def test_calculate_moving_averages(self): + prices = pd.Series([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], name='Price') + signals = calculate_moving_averages(prices, short_window=2, long_window=5) + self.assertIn('short_mavg', signals.columns) + self.assertIn('long_mavg', signals.columns) + # Check values: window=2, min_periods=1 + # Day 0: 10 + # Day 1: (10+11)/2 = 10.5 + self.assertEqual(signals['short_mavg'].iloc[1], 10.5) + + def test_generate_trading_signals(self): + # Create a dummy signals DataFrame + data = { + 'price': [100, 101, 102, 103], + 'short_mavg': [100, 105, 90, 95], + 'long_mavg': [100, 100, 100, 100] + } + signals = pd.DataFrame(data) + signals = generate_trading_signals(signals) + + self.assertIn('signal', signals.columns) + self.assertIn('positions', signals.columns) + + # Day 1: short (105) > long (100) -> signal 1.0 (Buy) + self.assertEqual(signals['signal'].iloc[1], 1.0) + + # Day 2: short (90) < long (100) -> signal -1.0 (Sell) + self.assertEqual(signals['signal'].iloc[2], -1.0) + + # Positions are shifted diff + # signal: 0, 1, -1, -1 + # diff: NaN, 1, -2, 0 + # shifted: NaN, NaN, 1, -2 + self.assertTrue(np.isnan(signals['positions'].iloc[0])) + self.assertTrue(np.isnan(signals['positions'].iloc[1])) + self.assertEqual(signals['positions'].iloc[2], 1.0) + self.assertEqual(signals['positions'].iloc[3], -2.0) + + def test_colors_disable(self): + # Save original values just in case + original_header = Colors.HEADER + + Colors.disable() + self.assertEqual(Colors.HEADER, '') + self.assertEqual(Colors.GREEN, '') + self.assertEqual(Colors.RED, '') + self.assertEqual(Colors.ENDC, '') + +if __name__ == '__main__': + unittest.main()