Skip to content

Commit dadbe2a

Browse files
authored
Merge pull request #62 from EiJackGH/palette-enhanced-cli-report-10784789726884231400
🎨 Palette: Enhance CLI Report with ASCII Dashboard & Stats
2 parents 0704834 + 82c75dc commit dadbe2a

8 files changed

Lines changed: 97 additions & 32 deletions

.Jules/palette.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
## 2024-05-23 - CLI UX Enhancement
22
**Learning:** Even in CLI apps, visual distinction (colors, emojis) significantly reduces cognitive load when scanning logs.
33
**Action:** Use ANSI colors and consistent emojis for key events (success/failure) in future CLI tools.
4+
5+
## 2025-02-28 - Structured CLI Reports
6+
**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.
7+
**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.
2.42 KB
Binary file not shown.

bitcoin.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import requests
2+
3+
4+
def get_bitcoin_price():
5+
"""
6+
Fetches the current Bitcoin price from CoinDesk API.
7+
"""
8+
url = "https://api.coindesk.com/v1/bpi/currentprice.json"
9+
try:
10+
response = requests.get(url)
11+
if response.status_code == 200:
12+
data = response.json()
13+
return data["bpi"]["USD"]["rate_float"]
14+
else:
15+
raise ConnectionError(f"API returned status code {response.status_code}")
16+
except requests.RequestException:
17+
raise ConnectionError("Failed to fetch Bitcoin price")
18+
19+
20+
def calculate_value(amount, price):
21+
"""
22+
Calculates the total value of Bitcoin based on the amount and current price.
23+
"""
24+
return float(amount) * float(price)

bitcoin_trading_simulation.py

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,32 @@
11
import argparse
22
import numpy as np
33
import pandas as pd
4-
import argparse
54

65

76
class Colors:
87
HEADER = '\033[95m'
98
BLUE = '\033[94m'
9+
CYAN = '\033[96m'
1010
GREEN = '\033[92m'
11-
RED = '\033[91m'
11+
WARNING = '\033[93m'
12+
FAIL = '\033[91m'
1213
ENDC = '\033[0m'
1314
BOLD = '\033[1m'
15+
UNDERLINE = '\033[4m'
1416

1517
@classmethod
1618
def disable(cls):
1719
cls.HEADER = ''
1820
cls.BLUE = ''
21+
cls.CYAN = ''
1922
cls.GREEN = ''
20-
cls.RED = ''
23+
cls.WARNING = ''
24+
cls.FAIL = ''
2125
cls.ENDC = ''
2226
cls.BOLD = ''
27+
cls.UNDERLINE = ''
2328

2429

25-
class Colors:
26-
HEADER = '\033[95m'
27-
BLUE = '\033[94m'
28-
CYAN = '\033[96m'
29-
GREEN = '\033[92m'
30-
WARNING = '\033[93m'
31-
FAIL = '\033[91m'
32-
ENDC = '\033[0m'
33-
BOLD = '\033[1m'
34-
UNDERLINE = '\033[4m'
35-
3630
def simulate_bitcoin_prices(days=60, initial_price=50000, volatility=0.02):
3731
"""
3832
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):
8781
portfolio['total_value'] = float(initial_cash)
8882

8983
if not quiet:
90-
print(f"{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}")
91-
92-
print(f"\n{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}")
84+
print(f"\n{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}")
9385
for i, row in signals.iterrows():
9486
if i > 0:
9587
portfolio.loc[i, 'cash'] = portfolio.loc[i-1, 'cash']
@@ -153,13 +145,51 @@ def simulate_trading(signals, initial_cash=10000, quiet=False):
153145
# Compare with buy and hold strategy
154146
buy_and_hold_btc = args.initial_cash / prices.iloc[0]
155147
buy_and_hold_value = buy_and_hold_btc * prices.iloc[-1]
156-
157-
print(f"\n{Colors.HEADER}{Colors.BOLD}------ Final Portfolio Performance ------{Colors.ENDC}")
158-
print(f"Initial Cash: ${initial_cash:.2f}")
159-
print(f"Final Portfolio Value: ${final_value:.2f}")
160-
if profit >= 0:
161-
print(f"{Colors.GREEN}💰 Profit/Loss: ${profit:.2f}{Colors.ENDC}")
162-
else:
163-
print(f"{Colors.FAIL}📉 Profit/Loss: ${profit:.2f}{Colors.ENDC}")
164-
print(f"Buy and Hold Strategy Value: ${buy_and_hold_value:.2f}")
165-
print(f"{Colors.HEADER}-----------------------------------------{Colors.ENDC}")
148+
149+
# Calculate additional statistics
150+
roi = (profit / initial_cash) * 100
151+
trade_count_buys = int(portfolio['btc'].diff().fillna(0).gt(0).sum())
152+
trade_count_sells = int(portfolio['btc'].diff().fillna(0).lt(0).sum())
153+
total_trades = trade_count_buys + trade_count_sells
154+
vs_buy_hold = final_value - buy_and_hold_value
155+
156+
# Format the final report
157+
width = 44
158+
border = "═" * width
159+
160+
print(f"\n{Colors.HEADER}{Colors.BOLD}{border}{Colors.ENDC}")
161+
title = "Final Portfolio Performance"
162+
print(f"{Colors.HEADER}{Colors.BOLD}{title:^{width}}{Colors.ENDC}")
163+
print(f"{Colors.HEADER}{Colors.BOLD}{border}{Colors.ENDC}")
164+
165+
def print_line(label, value_str, color=Colors.ENDC):
166+
left_border = f"{Colors.HEADER}{Colors.BOLD}{Colors.ENDC}"
167+
right_border = f"{Colors.HEADER}{Colors.BOLD}{Colors.ENDC}"
168+
print(f"{left_border} {label:<24}{color}{value_str:>18}{Colors.ENDC} {right_border}")
169+
170+
print_line("Initial Cash:", f"${initial_cash:,.2f}")
171+
print_line("Final Portfolio Value:", f"${final_value:,.2f}")
172+
173+
profit_color = Colors.GREEN if profit >= 0 else Colors.FAIL
174+
profit_sign = "+" if profit >= 0 else "-"
175+
print_line("Profit/Loss:", f"{profit_sign}${abs(profit):,.2f}", profit_color)
176+
177+
roi_color = Colors.GREEN if roi >= 0 else Colors.FAIL
178+
roi_sign = "+" if roi >= 0 else "-"
179+
print_line("ROI:", f"{roi_sign}{abs(roi):.2f}%", roi_color)
180+
181+
print(f"{Colors.HEADER}{Colors.BOLD}{border}{Colors.ENDC}")
182+
183+
print_line("Total Trades:", f"{total_trades}")
184+
print_line(" - Buys:", f"{trade_count_buys}")
185+
print_line(" - Sells:", f"{trade_count_sells}")
186+
187+
print(f"{Colors.HEADER}{Colors.BOLD}{border}{Colors.ENDC}")
188+
189+
print_line("Buy & Hold Value:", f"${buy_and_hold_value:,.2f}")
190+
191+
vs_color = Colors.GREEN if vs_buy_hold >= 0 else Colors.FAIL
192+
vs_sign = "+" if vs_buy_hold >= 0 else "-"
193+
print_line("vs Buy & Hold:", f"{vs_sign}${abs(vs_buy_hold):,.2f}", vs_color)
194+
195+
print(f"{Colors.HEADER}{Colors.BOLD}{border}{Colors.ENDC}")

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
numpy
22
pandas
3+
requests

test_bitcoin.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from unittest.mock import patch
33
from bitcoin import get_bitcoin_price, calculate_value
44

5+
56
# Test 1: Verify the calculation logic
67
def test_calculate_value():
78
"""Ensure BTC to USD conversion math is correct."""
@@ -10,10 +11,12 @@ def test_calculate_value():
1011
expected = 125000.0
1112
assert calculate_value(amount, price) == expected
1213

14+
1315
# Test 2: Verify handling of zero amount
1416
def test_calculate_value_zero():
1517
assert calculate_value(0, 50000.0) == 0.0
1618

19+
1720
# Test 3: Mocking an API response
1821
@patch('bitcoin.requests.get')
1922
def test_get_bitcoin_price(mock_get):
@@ -23,10 +26,11 @@ def test_get_bitcoin_price(mock_get):
2326
"bpi": {"USD": {"rate_float": 62000.50}}
2427
}
2528
mock_get.return_value.status_code = 200
26-
29+
2730
price = get_bitcoin_price()
2831
assert price == 62000.50
2932

33+
3034
# Test 4: Handling API failure
3135
@patch('bitcoin.requests.get')
3236
def test_get_price_api_error(mock_get):

test_bitcoin_trading.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def reset_colors():
1313
'HEADER': Colors.HEADER,
1414
'BLUE': Colors.BLUE,
1515
'GREEN': Colors.GREEN,
16-
'RED': Colors.RED,
16+
'FAIL': Colors.FAIL,
1717
'ENDC': Colors.ENDC,
1818
'BOLD': Colors.BOLD,
1919
}
@@ -22,7 +22,7 @@ def reset_colors():
2222
Colors.HEADER = original_colors['HEADER']
2323
Colors.BLUE = original_colors['BLUE']
2424
Colors.GREEN = original_colors['GREEN']
25-
Colors.RED = original_colors['RED']
25+
Colors.FAIL = original_colors['FAIL']
2626
Colors.ENDC = original_colors['ENDC']
2727
Colors.BOLD = original_colors['BOLD']
2828

@@ -58,7 +58,7 @@ def test_colors_disable(reset_colors):
5858
Colors.disable()
5959
assert Colors.HEADER == ""
6060
assert Colors.GREEN == ""
61-
assert Colors.RED == ""
61+
assert Colors.FAIL == ""
6262

6363

6464
def test_simulation_integration():

test_simulation.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
import pytest
21
import pandas as pd
32
import numpy as np
43
from bitcoin_trading_simulation import simulate_bitcoin_prices, calculate_moving_averages, generate_trading_signals
54

5+
66
def test_simulate_bitcoin_prices():
77
days = 10
88
prices = simulate_bitcoin_prices(days=days, initial_price=50000)
99
assert len(prices) == days
1010
assert isinstance(prices, pd.Series)
1111
assert prices.name == 'Price'
1212

13+
1314
def test_calculate_moving_averages():
1415
prices = pd.Series([100, 101, 102, 103, 104, 105, 106, 107, 108, 109], name='Price')
1516
signals = calculate_moving_averages(prices, short_window=3, long_window=5)
1617
assert 'short_mavg' in signals.columns
1718
assert 'long_mavg' in signals.columns
1819
assert not signals['short_mavg'].isnull().all()
1920

21+
2022
def test_generate_trading_signals():
2123
# Create dummy signals DataFrame
2224
data = {

0 commit comments

Comments
 (0)