diff --git a/.Jules/palette.md b/.Jules/palette.md index 018831f..25f18f6 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -1,3 +1,7 @@ ## 2024-05-23 - CLI UX Enhancement **Learning:** Even in CLI apps, visual distinction (colors, emojis) significantly reduces cognitive load when scanning logs. **Action:** Use ANSI colors and consistent emojis for key events (success/failure) in future CLI tools. + +## 2025-05-27 - CLI Accessibility & Robustness +**Learning:** Hardcoded ANSI colors without a disable mechanism exclude users with accessibility needs or those parsing logs. A crash when disabling colors is a critical failure. +**Action:** Always implement a robust `--no-color` flag and ensure `Colors` class handles disabling correctly. Input validation preventing runtime crashes (like ZeroDivisionError or negative inputs) is critical UX. diff --git a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc index 52abfc2..3eef49c 100644 Binary files a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc and b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc differ diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index c86be3e..7088ccd 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,38 +1,35 @@ import argparse import numpy as np import pandas as pd -import argparse +import sys class Colors: HEADER = '\033[95m' BLUE = '\033[94m' + CYAN = '\033[96m' GREEN = '\033[92m' - RED = '\033[91m' + WARNING = '\033[93m' + FAIL = '\033[91m' + RED = '\033[91m' # Alias for FAIL/RED compatibility ENDC = '\033[0m' BOLD = '\033[1m' + UNDERLINE = '\033[4m' @classmethod def disable(cls): cls.HEADER = '' cls.BLUE = '' + cls.CYAN = '' cls.GREEN = '' + cls.WARNING = '' + cls.FAIL = '' cls.RED = '' cls.ENDC = '' cls.BOLD = '' + cls.UNDERLINE = '' -class Colors: - HEADER = '\033[95m' - BLUE = '\033[94m' - CYAN = '\033[96m' - GREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - 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. @@ -87,9 +84,8 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): portfolio['total_value'] = float(initial_cash) if not quiet: - print(f"{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}") + print(f"\n{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}") - print(f"\n{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'] @@ -133,6 +129,20 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): if args.no_color: Colors.disable() + # Input Validation + 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 cannot be negative.{Colors.ENDC}") + sys.exit(1) + # Simulate prices prices = simulate_bitcoin_prices(days=args.days, initial_price=args.initial_price, volatility=args.volatility) @@ -149,17 +159,30 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): final_value = portfolio['total_value'].iloc[-1] initial_cash = args.initial_cash profit = final_value - initial_cash + roi = (profit / initial_cash) * 100 + + # Trade Statistics + # Calculate total trades by tracking changes in BTC holdings. + btc_diff = portfolio['btc'].diff().fillna(0) + buys = len(btc_diff[btc_diff > 0]) + sells = len(btc_diff[btc_diff < 0]) + total_trades = buys + sells # Compare with buy and hold strategy buy_and_hold_btc = args.initial_cash / prices.iloc[0] buy_and_hold_value = buy_and_hold_btc * prices.iloc[-1] - - print(f"\n{Colors.HEADER}{Colors.BOLD}------ Final Portfolio Performance ------{Colors.ENDC}") - print(f"Initial Cash: ${initial_cash:.2f}") - print(f"Final Portfolio Value: ${final_value:.2f}") + + print(f"\n{Colors.HEADER}╔════════════════════════════════════════╗{Colors.ENDC}") + print(f"{Colors.HEADER}║ Final Portfolio Performance ║{Colors.ENDC}") + print(f"{Colors.HEADER}╚════════════════════════════════════════╝{Colors.ENDC}") + print(f"Initial Cash: ${initial_cash:,.2f}") + print(f"Final Portfolio Value: ${final_value:,.2f}") if profit >= 0: - print(f"{Colors.GREEN}💰 Profit/Loss: ${profit:.2f}{Colors.ENDC}") + print(f"{Colors.GREEN}💰 Profit/Loss: ${profit:,.2f} ({roi:+.2f}%){Colors.ENDC}") else: - print(f"{Colors.FAIL}📉 Profit/Loss: ${profit:.2f}{Colors.ENDC}") - print(f"Buy and Hold Strategy Value: ${buy_and_hold_value:.2f}") - print(f"{Colors.HEADER}-----------------------------------------{Colors.ENDC}") + print(f"{Colors.FAIL}📉 Profit/Loss: ${profit:,.2f} ({roi:+.2f}%){Colors.ENDC}") + + print(f"{Colors.CYAN}------------------------------------------{Colors.ENDC}") + print(f"Total Trades: {total_trades} (Buys: {buys}, Sells: {sells})") + print(f"Buy and Hold Value: ${buy_and_hold_value:,.2f}") + print(f"{Colors.CYAN}------------------------------------------{Colors.ENDC}") diff --git a/test_bitcoin.py b/test_bitcoin.py deleted file mode 100644 index 163248c..0000000 --- a/test_bitcoin.py +++ /dev/null @@ -1,35 +0,0 @@ -import pytest -from unittest.mock import patch -from bitcoin import get_bitcoin_price, calculate_value - -# Test 1: Verify the calculation logic -def test_calculate_value(): - """Ensure BTC to USD conversion math is correct.""" - price = 50000.0 - amount = 2.5 - expected = 125000.0 - assert calculate_value(amount, price) == expected - -# Test 2: Verify handling of zero amount -def test_calculate_value_zero(): - assert calculate_value(0, 50000.0) == 0.0 - -# Test 3: Mocking an API response -@patch('bitcoin.requests.get') -def test_get_bitcoin_price(mock_get): - """Simulate a successful API response from CoinDesk or similar.""" - # Mock the JSON return value - mock_get.return_value.json.return_value = { - "bpi": {"USD": {"rate_float": 62000.50}} - } - mock_get.return_value.status_code = 200 - - price = get_bitcoin_price() - assert price == 62000.50 - -# Test 4: Handling API failure -@patch('bitcoin.requests.get') -def test_get_price_api_error(mock_get): - mock_get.return_value.status_code = 404 - with pytest.raises(ConnectionError): - get_bitcoin_price() diff --git a/test_simulation.py b/test_simulation.py index 0f4f1f8..8fddb57 100644 --- a/test_simulation.py +++ b/test_simulation.py @@ -1,8 +1,8 @@ -import pytest import pandas as pd import numpy as np from bitcoin_trading_simulation import simulate_bitcoin_prices, calculate_moving_averages, generate_trading_signals + def test_simulate_bitcoin_prices(): days = 10 prices = simulate_bitcoin_prices(days=days, initial_price=50000) @@ -10,6 +10,7 @@ def test_simulate_bitcoin_prices(): assert isinstance(prices, pd.Series) assert prices.name == 'Price' + def test_calculate_moving_averages(): prices = pd.Series([100, 101, 102, 103, 104, 105, 106, 107, 108, 109], name='Price') signals = calculate_moving_averages(prices, short_window=3, long_window=5) @@ -17,6 +18,7 @@ def test_calculate_moving_averages(): assert 'long_mavg' in signals.columns assert not signals['short_mavg'].isnull().all() + def test_generate_trading_signals(): # Create dummy signals DataFrame data = {