diff --git a/.Jules/palette.md b/.Jules/palette.md index 018831f..96bd45d 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-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. diff --git a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc index 52abfc2..81dd655 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.py b/bitcoin.py new file mode 100644 index 0000000..03b3371 --- /dev/null +++ b/bitcoin.py @@ -0,0 +1,24 @@ +import requests + + +def get_bitcoin_price(): + """ + Fetches the current Bitcoin price from CoinDesk API. + """ + url = "https://api.coindesk.com/v1/bpi/currentprice.json" + try: + response = requests.get(url) + if response.status_code == 200: + data = response.json() + return data["bpi"]["USD"]["rate_float"] + else: + raise ConnectionError(f"API returned status code {response.status_code}") + except requests.RequestException: + raise ConnectionError("Failed to fetch Bitcoin price") + + +def calculate_value(amount, price): + """ + Calculates the total value of Bitcoin based on the amount and current price. + """ + return float(amount) * float(price) diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index c86be3e..001e336 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,38 +1,32 @@ import argparse import numpy as np import pandas as pd -import argparse class Colors: HEADER = '\033[95m' BLUE = '\033[94m' + CYAN = '\033[96m' GREEN = '\033[92m' - RED = '\033[91m' + WARNING = '\033[93m' + FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' + UNDERLINE = '\033[4m' @classmethod def disable(cls): cls.HEADER = '' cls.BLUE = '' + cls.CYAN = '' cls.GREEN = '' - cls.RED = '' + cls.WARNING = '' + cls.FAIL = '' 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 +81,7 @@ 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'] @@ -153,13 +145,51 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): # 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}") - if profit >= 0: - print(f"{Colors.GREEN}💰 Profit/Loss: ${profit:.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}") + + # Calculate additional statistics + roi = (profit / initial_cash) * 100 + trade_count_buys = int(portfolio['btc'].diff().fillna(0).gt(0).sum()) + trade_count_sells = int(portfolio['btc'].diff().fillna(0).lt(0).sum()) + total_trades = trade_count_buys + trade_count_sells + vs_buy_hold = final_value - buy_and_hold_value + + # Format the final report + width = 44 + border = "═" * width + + print(f"\n{Colors.HEADER}{Colors.BOLD}╔{border}╗{Colors.ENDC}") + title = "Final Portfolio Performance" + print(f"{Colors.HEADER}{Colors.BOLD}║{title:^{width}}║{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}╠{border}╣{Colors.ENDC}") + + def print_line(label, value_str, color=Colors.ENDC): + left_border = f"{Colors.HEADER}{Colors.BOLD}║{Colors.ENDC}" + right_border = f"{Colors.HEADER}{Colors.BOLD}║{Colors.ENDC}" + print(f"{left_border} {label:<24}{color}{value_str:>18}{Colors.ENDC} {right_border}") + + print_line("Initial Cash:", f"${initial_cash:,.2f}") + print_line("Final Portfolio Value:", f"${final_value:,.2f}") + + profit_color = Colors.GREEN if profit >= 0 else Colors.FAIL + profit_sign = "+" if profit >= 0 else "-" + print_line("Profit/Loss:", f"{profit_sign}${abs(profit):,.2f}", profit_color) + + roi_color = Colors.GREEN if roi >= 0 else Colors.FAIL + roi_sign = "+" if roi >= 0 else "-" + print_line("ROI:", f"{roi_sign}{abs(roi):.2f}%", roi_color) + + print(f"{Colors.HEADER}{Colors.BOLD}╠{border}╣{Colors.ENDC}") + + print_line("Total Trades:", f"{total_trades}") + print_line(" - Buys:", f"{trade_count_buys}") + print_line(" - Sells:", f"{trade_count_sells}") + + print(f"{Colors.HEADER}{Colors.BOLD}╠{border}╣{Colors.ENDC}") + + print_line("Buy & Hold Value:", f"${buy_and_hold_value:,.2f}") + + vs_color = Colors.GREEN if vs_buy_hold >= 0 else Colors.FAIL + vs_sign = "+" if vs_buy_hold >= 0 else "-" + print_line("vs Buy & Hold:", f"{vs_sign}${abs(vs_buy_hold):,.2f}", vs_color) + + print(f"{Colors.HEADER}{Colors.BOLD}╚{border}╝{Colors.ENDC}") diff --git a/requirements.txt b/requirements.txt index 5da331c..4ad1501 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ numpy pandas +requests diff --git a/test_bitcoin.py b/test_bitcoin.py index 163248c..4785f33 100644 --- a/test_bitcoin.py +++ b/test_bitcoin.py @@ -2,6 +2,7 @@ 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.""" @@ -10,10 +11,12 @@ def test_calculate_value(): 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): @@ -23,10 +26,11 @@ def test_get_bitcoin_price(mock_get): "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): diff --git a/test_bitcoin_trading.py b/test_bitcoin_trading.py index e7eac6f..6c480ea 100644 --- a/test_bitcoin_trading.py +++ b/test_bitcoin_trading.py @@ -13,7 +13,7 @@ def reset_colors(): 'HEADER': Colors.HEADER, 'BLUE': Colors.BLUE, 'GREEN': Colors.GREEN, - 'RED': Colors.RED, + 'FAIL': Colors.FAIL, 'ENDC': Colors.ENDC, 'BOLD': Colors.BOLD, } @@ -22,7 +22,7 @@ def reset_colors(): Colors.HEADER = original_colors['HEADER'] Colors.BLUE = original_colors['BLUE'] Colors.GREEN = original_colors['GREEN'] - Colors.RED = original_colors['RED'] + Colors.FAIL = original_colors['FAIL'] Colors.ENDC = original_colors['ENDC'] Colors.BOLD = original_colors['BOLD'] @@ -58,7 +58,7 @@ def test_colors_disable(reset_colors): Colors.disable() assert Colors.HEADER == "" assert Colors.GREEN == "" - assert Colors.RED == "" + assert Colors.FAIL == "" def test_simulation_integration(): 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 = {