diff --git a/.Jules/palette.md b/.Jules/palette.md index 5de3a38..af9c96e 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-22 - CLI User Experience +**Learning:** CLI tools often lack basic customization and accessibility features like color toggles or quiet modes, making them harder to use in scripts or for users with visual impairments. +**Action:** Always check for and implement `argparse` with standard flags (`--quiet`, `--no-color`) in Python CLI tools to improve flexibility and accessibility. diff --git a/.gitignore b/.gitignore index d4fb281..c56a38d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,9 @@ # debug information files *.dwo + +# Python +__pycache__/ +*.py[cod] +*$py.class +.pytest_cache/ diff --git a/README.md b/README.md index be561ef..3b634df 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,27 @@ Run the simulation script: python bitcoin_trading_simulation.py ``` +### Options + +Customize the simulation with the following arguments: + +- `--days [int]`: Number of days to simulate (default: 60) +- `--initial-price [float]`: Initial Bitcoin price (default: 50000) +- `--volatility [float]`: Volatility factor (default: 0.02) +- `--initial-cash [float]`: Initial cash available (default: 10000) +- `--quiet`: Suppress daily ledger output +- `--no-color`: Disable colored output + +Example: + +```bash +python bitcoin_trading_simulation.py --days 100 --initial-cash 5000 --quiet +``` + ## Tests Run the test suite: ```bash -python test.py +pytest ``` diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 82df43f..96720e8 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 @@ -49,7 +50,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 +60,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 +72,41 @@ 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') + parser.add_argument('--initial-price', type=float, default=50000, help='Initial Bitcoin price') + parser.add_argument('--volatility', type=float, default=0.02, help='Volatility factor') + parser.add_argument('--initial-cash', type=float, default=10000, help='Initial cash available') + parser.add_argument('--quiet', action='store_true', help='Suppress daily ledger output') + parser.add_argument('--no-color', action='store_true', help='Disable colored output') + args = parser.parse_args() + + if args.no_color: + for attr in dir(Colors): + if not attr.startswith("__"): + setattr(Colors, attr, "") + # 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,19 +115,18 @@ 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 - profit = final_value - initial_cash + profit = final_value - args.initial_cash # Compare with buy and hold strategy - buy_and_hold_btc = initial_cash / prices.iloc[0] + 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"Initial Cash: ${args.initial_cash:.2f}") print(f"Final Portfolio Value: ${final_value:.2f}") if profit >= 0: diff --git a/test.py b/test.py deleted file mode 100644 index b326d87..0000000 --- a/test.py +++ /dev/null @@ -1,3 +0,0 @@ -# Filename: tests/test_sample.py -def test_example(): - assert 1 + 1 == 2 diff --git a/test_bitcoin_trading.py b/test_bitcoin_trading.py new file mode 100644 index 0000000..e3bb5dd --- /dev/null +++ b/test_bitcoin_trading.py @@ -0,0 +1,25 @@ +import pytest +import pandas as pd +from bitcoin_trading_simulation import simulate_bitcoin_prices, calculate_moving_averages, generate_trading_signals, simulate_trading + +def test_simulation_runs(): + # Run with small number of days for speed + prices = simulate_bitcoin_prices(days=20, initial_price=100) + assert isinstance(prices, pd.Series) + assert len(prices) == 20 + + signals = calculate_moving_averages(prices, short_window=5, long_window=10) + assert 'short_mavg' in signals.columns + assert 'long_mavg' in signals.columns + + signals = generate_trading_signals(signals) + assert 'signal' in signals.columns + assert 'positions' in signals.columns + + # We can't easily test simulate_trading output without capturing stdout, + # but we can check if it returns a portfolio DataFrame + portfolio = simulate_trading(signals, initial_cash=1000) + assert isinstance(portfolio, pd.DataFrame) + assert 'total_value' in portfolio.columns + # Basic sanity check that value isn't NaN or something weird + assert not portfolio['total_value'].isnull().any()