Skip to content

Commit bff9ec3

Browse files
authored
Merge pull request #38 from EiJackGH/palette-cli-accessibility-18133986709458003954
🎨 Palette: CLI Accessibility & Usability Upgrade
2 parents 1b473de + 0857f57 commit bff9ec3

6 files changed

Lines changed: 131 additions & 12 deletions

.Jules/palette.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
**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.
33
**Action:** For data-heavy CLI applications, always implement a semantic color system and visual anchors (icons/emojis) for key events.
44

5-
## 2026-02-06 - CLI Accessibility Standards
6-
**Learning:** CLI tools are surprisingly inaccessible without standard flags like `--no-color` and `--quiet`. Users expect these controls to integrate with scripts and accessibility tools.
7-
**Action:** Always implement `argparse` with quiet/color-disable options for any CLI outputting more than 5 lines.
5+
## 2026-02-11 - Accessibility in CLI Tools
6+
**Learning:** Hardcoded ANSI colors exclude users with visual impairments or conflicting terminal themes. Providing a standard `--no-color` flag is a zero-cost accessibility win that respects user preference.
7+
**Action:** Always wrap color codes in a configurable class and expose a disable mechanism via CLI arguments.

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,33 @@ pip install -r requirements.txt
2020

2121
## Usage
2222

23-
Run the simulation script:
23+
Run the simulation script with default settings:
2424

2525
```bash
2626
python bitcoin_trading_simulation.py
2727
```
2828

29+
### Options
30+
31+
You can customize the simulation with the following arguments:
32+
33+
- `--days`: Number of days to simulate (default: 60)
34+
- `--initial-cash`: Initial cash amount (default: 10000)
35+
- `--initial-price`: Initial Bitcoin price (default: 50000)
36+
- `--volatility`: Volatility factor (default: 0.02)
37+
- `--quiet`: Suppress daily ledger output (show only final results)
38+
- `--no-color`: Disable colored output (for accessibility)
39+
40+
**Example:**
41+
42+
```bash
43+
python bitcoin_trading_simulation.py --days 100 --initial-cash 5000 --quiet
44+
```
45+
2946
## Tests
3047

3148
Run the test suite:
3249

3350
```bash
34-
python test.py
51+
python -m unittest test_bitcoin_trading.py
3552
```
8.21 KB
Binary file not shown.
5.05 KB
Binary file not shown.

bitcoin_trading_simulation.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import argparse
2+
import sys
13
import numpy as np
24
import pandas as pd
35
import argparse
@@ -102,10 +104,10 @@ def simulate_trading(signals, initial_cash=10000, quiet=False):
102104
if __name__ == "__main__":
103105
parser = argparse.ArgumentParser(description='Bitcoin Trading Simulation')
104106
parser.add_argument('--days', type=int, default=60, help='Number of days to simulate')
105-
parser.add_argument('--initial-cash', type=float, default=10000, help='Initial cash amount')
106-
parser.add_argument('--initial-price', type=float, default=50000, help='Initial Bitcoin price')
107+
parser.add_argument('--initial-cash', type=float, default=10000.0, help='Initial cash amount')
108+
parser.add_argument('--initial-price', type=float, default=50000.0, help='Initial Bitcoin price')
107109
parser.add_argument('--volatility', type=float, default=0.02, help='Volatility factor')
108-
parser.add_argument('--quiet', action='store_true', help='Suppress daily output')
110+
parser.add_argument('--quiet', action='store_true', help='Suppress daily ledger output')
109111
parser.add_argument('--no-color', action='store_true', help='Disable colored output')
110112

111113
args = parser.parse_args()
@@ -127,15 +129,14 @@ def simulate_trading(signals, initial_cash=10000, quiet=False):
127129

128130
# Final portfolio performance
129131
final_value = portfolio['total_value'].iloc[-1]
130-
initial_cash = args.initial_cash
131-
profit = final_value - initial_cash
132+
profit = final_value - args.initial_cash
132133

133134
# Compare with buy and hold strategy
134-
buy_and_hold_btc = initial_cash / prices.iloc[0]
135+
buy_and_hold_btc = args.initial_cash / prices.iloc[0]
135136
buy_and_hold_value = buy_and_hold_btc * prices.iloc[-1]
136137

137138
print(f"\n{Colors.HEADER}{Colors.BOLD}------ Final Portfolio Performance ------{Colors.ENDC}")
138-
print(f"Initial Cash: ${initial_cash:.2f}")
139+
print(f"Initial Cash: ${args.initial_cash:.2f}")
139140
print(f"Final Portfolio Value: ${final_value:.2f}")
140141

141142
if profit >= 0:

test_bitcoin_trading.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import unittest
2+
import sys
3+
import io
4+
import pandas as pd
5+
import subprocess
6+
from bitcoin_trading_simulation import Colors, simulate_trading, simulate_bitcoin_prices
7+
8+
class TestBitcoinTradingSimulation(unittest.TestCase):
9+
10+
def setUp(self):
11+
# Reset Colors to default before each test to prevent side effects
12+
Colors.HEADER = '\033[95m'
13+
Colors.BLUE = '\033[94m'
14+
Colors.GREEN = '\033[92m'
15+
Colors.RED = '\033[91m'
16+
Colors.ENDC = '\033[0m'
17+
Colors.BOLD = '\033[1m'
18+
19+
def test_simulate_bitcoin_prices(self):
20+
prices = simulate_bitcoin_prices(days=10, initial_price=100)
21+
self.assertEqual(len(prices), 10)
22+
self.assertEqual(prices.iloc[0], 100)
23+
24+
def test_colors_disable(self):
25+
Colors.disable()
26+
self.assertEqual(Colors.HEADER, '')
27+
self.assertEqual(Colors.GREEN, '')
28+
self.assertEqual(Colors.RED, '')
29+
30+
def test_quiet_mode_function(self):
31+
# Create dummy signals
32+
prices = pd.Series([100, 101, 102], name='Price')
33+
signals = pd.DataFrame(index=prices.index)
34+
signals['price'] = prices
35+
signals['positions'] = 0.0
36+
37+
# Capture stdout
38+
captured_output = io.StringIO()
39+
sys.stdout = captured_output
40+
41+
simulate_trading(signals, quiet=True)
42+
43+
sys.stdout = sys.__stdout__
44+
output = captured_output.getvalue()
45+
46+
# Expect no output in quiet mode (since no trades and quiet=True)
47+
self.assertEqual(output, "")
48+
49+
def test_verbose_mode_function(self):
50+
# Create dummy signals
51+
prices = pd.Series([100, 101, 102], name='Price')
52+
signals = pd.DataFrame(index=prices.index)
53+
signals['price'] = prices
54+
signals['positions'] = 0.0
55+
56+
# Capture stdout
57+
captured_output = io.StringIO()
58+
sys.stdout = captured_output
59+
60+
simulate_trading(signals, quiet=False)
61+
62+
sys.stdout = sys.__stdout__
63+
output = captured_output.getvalue()
64+
65+
self.assertIn("Daily Trading Ledger", output)
66+
67+
def test_integration_no_color(self):
68+
# Run the script via subprocess to verify --no-color
69+
result = subprocess.run(
70+
[sys.executable, 'bitcoin_trading_simulation.py', '--days', '5', '--no-color'],
71+
capture_output=True,
72+
text=True
73+
)
74+
# Check that ANSI codes are NOT present
75+
self.assertNotIn('\033[', result.stdout)
76+
# Check that the output is still meaningful
77+
self.assertIn('Final Portfolio Performance', result.stdout)
78+
79+
def test_integration_quiet(self):
80+
# Run the script via subprocess to verify --quiet
81+
result = subprocess.run(
82+
[sys.executable, 'bitcoin_trading_simulation.py', '--days', '5', '--quiet'],
83+
capture_output=True,
84+
text=True
85+
)
86+
# Check that Daily Ledger is NOT present
87+
self.assertNotIn('Daily Trading Ledger', result.stdout)
88+
# Check that Final Performance IS present
89+
self.assertIn('Final Portfolio Performance', result.stdout)
90+
91+
def test_integration_args(self):
92+
# Verify custom arguments work
93+
result = subprocess.run(
94+
[sys.executable, 'bitcoin_trading_simulation.py', '--days', '5', '--initial-cash', '500'],
95+
capture_output=True,
96+
text=True
97+
)
98+
self.assertIn('Initial Cash: $500.00', result.stdout)
99+
100+
if __name__ == '__main__':
101+
unittest.main()

0 commit comments

Comments
 (0)