From 6ec37fbb1dfe6e23c0a8f9f15ae32e04cdada357 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:04:02 +0000 Subject: [PATCH 1/7] feat: improve CLI UX with colors, emojis and fix warnings - Added ANSI colors and emojis to `bitcoin_trading_simulation.py` to distinguish buy/sell events and profit/loss. - Fixed a pandas `FutureWarning` by explicitly casting `cash` to float. --- .Jules/palette.md | 3 +++ bitcoin_trading_simulation.py | 28 +++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 .Jules/palette.md diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..018831f --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,3 @@ +## 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. diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index e619723..4a80fc1 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,6 +1,17 @@ import numpy as np import pandas as pd +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. @@ -47,11 +58,11 @@ def simulate_trading(signals, initial_cash=10000): """ portfolio = pd.DataFrame(index=signals.index).fillna(0.0) portfolio['price'] = signals['price'] - portfolio['cash'] = initial_cash + portfolio['cash'] = float(initial_cash) portfolio['btc'] = 0.0 portfolio['total_value'] = portfolio['cash'] - print("------ Daily Trading Ledger ------") + 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'] @@ -62,14 +73,14 @@ 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"Day {i}: Buy {btc_to_buy:.4f} BTC at ${row['price']:.2f}") + 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"Day {i}: Sell {portfolio.loc[i, 'btc']:.4f} BTC at ${row['price']:.2f}") + print(f"{Colors.FAIL}🔴 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'] @@ -99,9 +110,12 @@ def simulate_trading(signals, initial_cash=10000): buy_and_hold_btc = initial_cash / prices.iloc[0] buy_and_hold_value = buy_and_hold_btc * prices.iloc[-1] - print("\n------ Final Portfolio Performance ------") + 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"Profit/Loss: ${profit:.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("-----------------------------------------") + print(f"{Colors.HEADER}-----------------------------------------{Colors.ENDC}") From 78055d6279daf43127230b20d79a7796d9b8bbe6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:32:30 +0000 Subject: [PATCH 2/7] feat: add CLI arguments for configuration and accessibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement `argparse` to allow configuring simulation parameters (`--days`, `--initial-cash`, etc.) - Add `--quiet` flag to suppress daily output for cleaner logs. - Add `--no-color` flag to disable ANSI codes for accessibility. - Update `Colors` class to support disabling. - Add `test_simulation.py` with proper unit tests. - Update `.gitignore` to exclude `__pycache__` and `*.pyc`. - Update `.Jules/palette.md` with UX learnings. 🎨 Palette: Make CLI intuitive and accessible. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 ++++ .gitignore | 4 ++++ bitcoin_trading_simulation.py | 43 ++++++++++++++++++++++++++++------- test.py | 3 --- test_simulation.py | 33 +++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 11 deletions(-) delete mode 100644 test.py create mode 100644 test_simulation.py diff --git a/.Jules/palette.md b/.Jules/palette.md index 5de3a38..06c2a65 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. + +## 2026-02-06 - CLI Accessibility Standards +**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. +**Action:** Always implement `argparse` with quiet/color-disable options for any CLI outputting more than 5 lines. diff --git a/.gitignore b/.gitignore index d4fb281..907591b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ # debug information files *.dwo + +# Python +__pycache__/ +*.pyc diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 82df43f..a759ec6 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,5 +1,6 @@ import numpy as np import pandas as pd +import argparse class Colors: HEADER = '\033[95m' @@ -9,6 +10,15 @@ class Colors: ENDC = '\033[0m' BOLD = '\033[1m' + @classmethod + def disable(cls): + cls.HEADER = '' + cls.BLUE = '' + cls.GREEN = '' + cls.RED = '' + cls.ENDC = '' + cls.BOLD = '' + 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. @@ -49,7 +59,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 +69,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 +81,40 @@ 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-cash', type=float, default=10000, help='Initial cash amount') + 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('--quiet', action='store_true', help='Suppress daily output') + parser.add_argument('--no-color', action='store_true', help='Disable colored output') + + args = parser.parse_args() + + if args.no_color: + Colors.disable() + # 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,11 +123,11 @@ 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 + initial_cash = args.initial_cash profit = final_value - initial_cash # Compare with buy and hold strategy 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_simulation.py b/test_simulation.py new file mode 100644 index 0000000..0f4f1f8 --- /dev/null +++ b/test_simulation.py @@ -0,0 +1,33 @@ +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) + assert len(prices) == days + 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) + assert 'short_mavg' in signals.columns + assert 'long_mavg' in signals.columns + assert not signals['short_mavg'].isnull().all() + +def test_generate_trading_signals(): + # Create dummy signals DataFrame + data = { + 'price': [100, 101, 102, 103, 104], + 'short_mavg': [100, 101, 105, 102, 100], + 'long_mavg': [100, 100, 100, 103, 105] + } + signals = pd.DataFrame(data) + signals = generate_trading_signals(signals) + + assert 'signal' in signals.columns + assert 'positions' in signals.columns + # Check that positions are calculated (not all nan, though first might be) + assert signals['positions'].isin([0, 1, -1, 2, -2, np.nan]).any() From de186f73c03cc0722f0145fe57b7c2a3581d3c60 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:22:27 +0000 Subject: [PATCH 3/7] feat: add CLI arguments and accessibility options Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 ++ .gitignore | 4 ++ README.md | 31 ++++++++++++--- bitcoin_trading_simulation.py | 62 ++++++++++++++++++++++------- test.py | 3 -- test_bitcoin_trading.py | 74 +++++++++++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 22 deletions(-) delete mode 100644 test.py create mode 100644 test_bitcoin_trading.py diff --git a/.Jules/palette.md b/.Jules/palette.md index 5de3a38..1279bd2 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-23 - CLI Accessibility and Control +**Learning:** While color and emojis enhance UX, they can be inaccessible (color blindness) or intrusive (automation logs). Providing `--no-color` and `--quiet` flags is essential for a robust CLI tool that respects user context and accessibility needs. +**Action:** Always include flags to disable visual enhancements and suppress verbose output in CLI tools. diff --git a/.gitignore b/.gitignore index d4fb281..907591b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ # debug information files *.dwo + +# Python +__pycache__/ +*.pyc diff --git a/README.md b/README.md index be561ef..4cd8db6 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,11 @@ A Python-based CLI tool that simulates Bitcoin trading using a 'Golden Cross' mo ## Features -- **Price Simulation:** Uses Geometric Brownian Motion to simulate 60 days of Bitcoin prices. +- **Price Simulation:** Uses Geometric Brownian Motion to simulate Bitcoin prices. - **Trading Strategy:** Implements a Golden Cross strategy (Short MA > Long MA = Buy, Short MA < Long MA = Sell). -- **Rich CLI Output:** features color-coded logs (Green for Buy/Profit, Red for Sell/Loss) and emojis for better readability. +- **Rich CLI Output:** Features color-coded logs (Green for Buy/Profit, Red for Sell/Loss) and emojis for better readability. - **Performance metrics:** Compares the strategy's performance against a "Buy and Hold" approach. +- **Customizable:** Configure simulation parameters via command-line arguments. ## Installation @@ -20,16 +21,36 @@ pip install -r requirements.txt ## Usage -Run the simulation script: +Run the simulation script with default settings (60 days, $10k initial cash): ```bash python bitcoin_trading_simulation.py ``` +### Options + +Customize the simulation with the following arguments: + +```bash +python bitcoin_trading_simulation.py --days 100 --initial-cash 5000 --initial-price 60000 --volatility 0.03 +``` + +- `--days`: Number of days to simulate (default: 60) +- `--initial-cash`: Initial cash amount (default: 10000) +- `--initial-price`: Initial Bitcoin price (default: 50000) +- `--volatility`: Price volatility (default: 0.02) +- `--quiet`: Suppress daily portfolio log (only show final result) +- `--no-color`: Disable colored output (for accessibility or logging) + +Example: +```bash +python bitcoin_trading_simulation.py --days 30 --quiet --no-color +``` + ## Tests -Run the test suite: +Run the test suite using `pytest`: ```bash -python test.py +pytest ``` diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 82df43f..fa1ccba 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,6 +1,8 @@ +import argparse import numpy as np import pandas as pd + class Colors: HEADER = '\033[95m' BLUE = '\033[94m' @@ -9,6 +11,16 @@ class Colors: ENDC = '\033[0m' BOLD = '\033[1m' + @classmethod + def disable(cls): + cls.HEADER = '' + cls.BLUE = '' + cls.GREEN = '' + cls.RED = '' + cls.ENDC = '' + cls.BOLD = '' + + 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. @@ -23,6 +35,7 @@ def simulate_bitcoin_prices(days=60, initial_price=50000, volatility=0.02): prices.append(prices[-1] + price_change) return pd.Series(prices, name='Price') + def calculate_moving_averages(prices, short_window=7, long_window=30): """ Calculates short and long moving averages for a given price series. @@ -33,6 +46,7 @@ def calculate_moving_averages(prices, short_window=7, long_window=30): signals['long_mavg'] = prices.rolling(window=long_window, min_periods=1, center=False).mean() return signals + def generate_trading_signals(signals): """ Generates trading signals based on the Golden Cross strategy. @@ -44,12 +58,13 @@ def generate_trading_signals(signals): signals.loc[signals['short_mavg'] > signals['long_mavg'], 'signal'] = 1.0 # A Death Cross (sell signal) signals.loc[signals['short_mavg'] < signals['long_mavg'], 'signal'] = -1.0 - + # We create 'positions' to represent the trading action: 1 for buy, -1 for sell, 0 for hold 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 +74,9 @@ 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'] @@ -81,32 +98,49 @@ def simulate_trading(signals, initial_cash=10000): 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}, " + f"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-cash", type=float, default=10000, help="Initial cash amount") + parser.add_argument("--initial-price", type=float, default=50000, help="Initial Bitcoin price") + parser.add_argument("--volatility", type=float, default=0.02, help="Price volatility") + parser.add_argument("--quiet", action="store_true", help="Suppress daily portfolio log") + parser.add_argument("--no-color", action="store_true", help="Disable colored output") + + args = parser.parse_args() + + if args.no_color: + Colors.disable() + # 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) - + # Generate trading signals 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 + initial_cash = args.initial_cash profit = final_value - initial_cash - + # Compare with buy and hold strategy buy_and_hold_btc = 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}") 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..e7eac6f --- /dev/null +++ b/test_bitcoin_trading.py @@ -0,0 +1,74 @@ +import pytest +import pandas as pd +from bitcoin_trading_simulation import ( + simulate_trading, Colors, calculate_moving_averages, + generate_trading_signals, simulate_bitcoin_prices +) + + +@pytest.fixture +def reset_colors(): + # Save original colors + original_colors = { + 'HEADER': Colors.HEADER, + 'BLUE': Colors.BLUE, + 'GREEN': Colors.GREEN, + 'RED': Colors.RED, + 'ENDC': Colors.ENDC, + 'BOLD': Colors.BOLD, + } + yield + # Restore colors + Colors.HEADER = original_colors['HEADER'] + Colors.BLUE = original_colors['BLUE'] + Colors.GREEN = original_colors['GREEN'] + Colors.RED = original_colors['RED'] + Colors.ENDC = original_colors['ENDC'] + Colors.BOLD = original_colors['BOLD'] + + +def test_simulate_trading_quiet_mode(capsys): + """Test that quiet mode suppresses output.""" + signals = pd.DataFrame(index=range(5)) + signals['price'] = [100.0, 101.0, 102.0, 103.0, 104.0] + signals['positions'] = [0.0] * 5 + + simulate_trading(signals, initial_cash=1000, quiet=True) + + captured = capsys.readouterr() + assert captured.out == "" + + +def test_simulate_trading_verbose_mode(capsys): + """Test that verbose mode prints daily ledger.""" + signals = pd.DataFrame(index=range(2)) + signals['price'] = [100.0, 101.0] + signals['positions'] = [0.0, 0.0] + + simulate_trading(signals, initial_cash=1000, quiet=False) + + captured = capsys.readouterr() + assert "Daily Trading Ledger" in captured.out + assert "Portfolio Value" in captured.out + + +def test_colors_disable(reset_colors): + """Test that Colors.disable() clears color codes.""" + assert Colors.HEADER != "" + Colors.disable() + assert Colors.HEADER == "" + assert Colors.GREEN == "" + assert Colors.RED == "" + + +def test_simulation_integration(): + """Test full simulation pipeline with small parameters.""" + prices = simulate_bitcoin_prices(days=10, initial_price=100) + signals = calculate_moving_averages(prices, short_window=2, long_window=5) + signals = generate_trading_signals(signals) + portfolio = simulate_trading(signals, quiet=True) + + assert len(portfolio) == 10 + assert 'total_value' in portfolio.columns + assert 'btc' in portfolio.columns + assert 'cash' in portfolio.columns From 3d19360444d4aad4aff9397eada895a0fc3c17e8 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:36:57 +0000 Subject: [PATCH 4/7] feat: Add CLI arguments for configuration and accessibility - Implements `argparse` to allow custom simulation parameters. - Adds `--quiet` flag to suppress daily ledger output. - Adds `--no-color` flag to disable ANSI color codes for accessibility. - Updates `README.md` with usage instructions. - Adds `test_bitcoin_trading.py` for unit and integration testing. - Updates `.gitignore` to exclude Python cache files. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 + .gitignore | 4 + README.md | 21 +++- ...bitcoin_trading_simulation.cpython-312.pyc | Bin 0 -> 8406 bytes .../test_bitcoin_trading.cpython-312.pyc | Bin 0 -> 5175 bytes bitcoin_trading_simulation.py | 49 +++++++-- test_bitcoin_trading.py | 101 ++++++++++++++++++ 7 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 __pycache__/bitcoin_trading_simulation.cpython-312.pyc create mode 100644 __pycache__/test_bitcoin_trading.cpython-312.pyc create mode 100644 test_bitcoin_trading.py diff --git a/.Jules/palette.md b/.Jules/palette.md index 5de3a38..9eea189 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. + +## 2026-02-11 - Accessibility in CLI Tools +**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. +**Action:** Always wrap color codes in a configurable class and expose a disable mechanism via CLI arguments. diff --git a/.gitignore b/.gitignore index d4fb281..907591b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ # debug information files *.dwo + +# Python +__pycache__/ +*.pyc diff --git a/README.md b/README.md index be561ef..6f2e3e3 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,33 @@ pip install -r requirements.txt ## Usage -Run the simulation script: +Run the simulation script with default settings: ```bash python bitcoin_trading_simulation.py ``` +### Options + +You can customize the simulation with the following arguments: + +- `--days`: Number of days to simulate (default: 60) +- `--initial-cash`: Initial cash amount (default: 10000) +- `--initial-price`: Initial Bitcoin price (default: 50000) +- `--volatility`: Volatility factor (default: 0.02) +- `--quiet`: Suppress daily ledger output (show only final results) +- `--no-color`: Disable colored output (for accessibility) + +**Example:** + +```bash +python bitcoin_trading_simulation.py --days 100 --initial-cash 5000 --quiet +``` + ## Tests Run the test suite: ```bash -python test.py +python -m unittest test_bitcoin_trading.py ``` diff --git a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52abfc2edcf23a102e58b9d12c1192ee1c250d1b GIT binary patch literal 8406 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 literal 0 HcmV?d00001 diff --git a/__pycache__/test_bitcoin_trading.cpython-312.pyc b/__pycache__/test_bitcoin_trading.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecc297a91549969cdf54cd01999ccc2da4eaaba9 GIT binary patch literal 5175 zcmeHLU2GKB6~6Pkv+K2CZEUZ-HY`7-Y+#M~F$ocY1Z)B=EW|j3E<`lic&?gokYUvOKgEu^3qi5OCO{5#cpk}8ju>PNEK2akl~4^o^xks*Ear1 zr1Yu1mcMi7&OP_=-XKCn$^#OKG;D+EmlcOrKLg~@S?us!Z3NeVg-DB>1igCd};OMpII22^w(pkG%2 z1B3opz-j6-Ec>WCSC_kpbMB(dY^KMX2`A*?A26nk= z)Q)_jirH!dbhKqBjE_09qo?Tk!U{#?PpH4PjQZD8L3_icNTrwv|Ae( z*loj6sU9yy###mj9mz86_XqM@Ry)cT)(1`4+68cfd>h^}B9BxS>Y^Yj3iX>o1PisB zKm-bPC@Prvb;Wb&PZm*u$HJ)iEmTH{T|RzJ=kE*miU)t48zTLsgU z^(Md#@;J0(vir}%Y^Z%$E-1l=N_a*Ie_k_o{}^ zDUPv-FEnn(*T12%kgxudi@=LwDMt<9p~RH;mXNX|EX&7Y9#_{_%AQ>dP5-#!fdEMRS@#aZ>R@u3*t!cbzg3c7d&Z&`4)rgTgqoq+wW=1!zIT8yO|MEt2`~z7FB2PABbz!HEa;9O^ zP4GGvboc??k5&KZv!R2-^5g0aqr2|x znmGUEr7tecR_`756(XC5eZTQRq$_ON0=0@6rRjxCBF%Sqj_>>|KD@3_yKO`m2|Q84 zWA&3Fgu5rT;n6F1u3%#!6nz+q&4glq5A7V5pUK2u=|;`5x{2yJW!wL~2Ux)J-}N3n zFeEPV9=a7~rP~pHj1WWEiGbM%jRPE~Er2gKv}(0B{udYCoBw2jOw4sW&oHziQC#tYs`Q_Se@o_GJ}ujn*#! zet2`2=9HEd&HY;?c)Rje<#gSy$=*4o?NwXH_{y??zd&WJo5IF(WM%nVUgvc|7yBd~ z{_e}(R|(7bO0nM?0d58#+#}1~1jYP=<$Ul}wY2EUT6z$h{fU>&OR{3Mn3zG(f&}@c z9FMaUkZa2a;_*y2&Ti?hDc-3n#zZe9inejh&hwFrFC)iIh?>!}S!(xYQ)V`L)}Xyv zn$|K9^L-)@WvIqmODBaMes!WlPN;Slbwv(>n$;TAcuBi;>ZdcQ!t*Tz~t@tt->bhyM8C zH^MiUrh~~sWK$tj`@$zy2Oww$%R#e#V*8izFXFRGYst3uJvjgM<*zQ!DaZbo?2F<# z(sqOnfV?jrXZKteJ&tWh5Kbce1mO(8B3|oW;gxCU_kBN7nGzfP9L%s1qd%Jtey|dw z(+#^H)P3FjRr9QJl$@$~y!fRT@6oIe z9igqY)g>xld(!;`j2?p*Z$`Jlo0isA_&)e4Y>%EoU>C`0ti6rE{Pq<>YhNK$qkUF> z@Au;qbuPjr)=_{J=$t-%d0N${gT`V?1K^}?u#Zn({r&Z+>vPIm%pnu83P({@Oh_Rs zgbWc9xLgYBK~=@iTlWpc?3p$rY({ts;T?qc5YWEpd4MUxMrC;+#ruN+S_S(R;U<|E zeNuG3T98`i1ELh04~C>4&2L{X9hl!1klMNVFyiyDPde-lgk#_W(;ED=q{gZoB?pw? zak?juH#&T8YZ+a$+$$fcaT-^S@9f*oz4bAVbrd84_%+=1et>NvPDhhaEpNwB4!LJ@ zm}>BXgMSNs_?f_Q+#|B?5eYmZ{zs(d5efZ+T$mvjUWiGKJ3R5m!>#*gw(frczj|?p hpIzgL7#z<3O=u{Hp&KWDb^7DezdZ9yAYvn%>Oa%SLudd1 literal 0 HcmV?d00001 diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 82df43f..631333d 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,3 +1,5 @@ +import argparse +import sys import numpy as np import pandas as pd @@ -9,6 +11,15 @@ class Colors: ENDC = '\033[0m' BOLD = '\033[1m' + @classmethod + def disable(cls): + cls.HEADER = '' + cls.BLUE = '' + cls.GREEN = '' + cls.RED = '' + cls.ENDC = '' + cls.BOLD = '' + 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. @@ -49,7 +60,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 +70,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 +82,40 @@ 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-cash', type=float, default=10000.0, help='Initial cash amount') + parser.add_argument('--initial-price', type=float, default=50000.0, help='Initial Bitcoin price') + parser.add_argument('--volatility', type=float, default=0.02, help='Volatility factor') + 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: + Colors.disable() + # 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 +124,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_bitcoin_trading.py b/test_bitcoin_trading.py new file mode 100644 index 0000000..9a63e5d --- /dev/null +++ b/test_bitcoin_trading.py @@ -0,0 +1,101 @@ +import unittest +import sys +import io +import pandas as pd +import subprocess +from bitcoin_trading_simulation import Colors, simulate_trading, simulate_bitcoin_prices + +class TestBitcoinTradingSimulation(unittest.TestCase): + + def setUp(self): + # Reset Colors to default before each test to prevent side effects + Colors.HEADER = '\033[95m' + Colors.BLUE = '\033[94m' + Colors.GREEN = '\033[92m' + Colors.RED = '\033[91m' + Colors.ENDC = '\033[0m' + Colors.BOLD = '\033[1m' + + def test_simulate_bitcoin_prices(self): + prices = simulate_bitcoin_prices(days=10, initial_price=100) + self.assertEqual(len(prices), 10) + self.assertEqual(prices.iloc[0], 100) + + def test_colors_disable(self): + Colors.disable() + self.assertEqual(Colors.HEADER, '') + self.assertEqual(Colors.GREEN, '') + self.assertEqual(Colors.RED, '') + + def test_quiet_mode_function(self): + # Create dummy signals + prices = pd.Series([100, 101, 102], name='Price') + signals = pd.DataFrame(index=prices.index) + signals['price'] = prices + signals['positions'] = 0.0 + + # Capture stdout + captured_output = io.StringIO() + sys.stdout = captured_output + + simulate_trading(signals, quiet=True) + + sys.stdout = sys.__stdout__ + output = captured_output.getvalue() + + # Expect no output in quiet mode (since no trades and quiet=True) + self.assertEqual(output, "") + + def test_verbose_mode_function(self): + # Create dummy signals + prices = pd.Series([100, 101, 102], name='Price') + signals = pd.DataFrame(index=prices.index) + signals['price'] = prices + signals['positions'] = 0.0 + + # Capture stdout + captured_output = io.StringIO() + sys.stdout = captured_output + + simulate_trading(signals, quiet=False) + + sys.stdout = sys.__stdout__ + output = captured_output.getvalue() + + self.assertIn("Daily Trading Ledger", output) + + def test_integration_no_color(self): + # Run the script via subprocess to verify --no-color + result = subprocess.run( + [sys.executable, 'bitcoin_trading_simulation.py', '--days', '5', '--no-color'], + capture_output=True, + text=True + ) + # Check that ANSI codes are NOT present + self.assertNotIn('\033[', result.stdout) + # Check that the output is still meaningful + self.assertIn('Final Portfolio Performance', result.stdout) + + def test_integration_quiet(self): + # Run the script via subprocess to verify --quiet + result = subprocess.run( + [sys.executable, 'bitcoin_trading_simulation.py', '--days', '5', '--quiet'], + capture_output=True, + text=True + ) + # Check that Daily Ledger is NOT present + self.assertNotIn('Daily Trading Ledger', result.stdout) + # Check that Final Performance IS present + self.assertIn('Final Portfolio Performance', result.stdout) + + def test_integration_args(self): + # Verify custom arguments work + result = subprocess.run( + [sys.executable, 'bitcoin_trading_simulation.py', '--days', '5', '--initial-cash', '500'], + capture_output=True, + text=True + ) + self.assertIn('Initial Cash: $500.00', result.stdout) + +if __name__ == '__main__': + unittest.main() From 806c9e6c985c9b193d3c1ce3d7125c03fe561fdf Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 13:39:28 +0000 Subject: [PATCH 5/7] feat: enhance CLI UX with argument parsing and accessibility options - Added `argparse` to allow configuration of days, initial cash, initial price, and volatility. - Implemented `--quiet` flag to suppress daily output. - Implemented `--no-color` flag to disable ANSI color codes for accessibility. - Refactored simulation logic into a `main` function for better testability. - Added unit tests in `test_bitcoin_trading.py`. - Updated `.gitignore` to exclude Python cache files. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 +++ .gitignore | 5 +++ bitcoin_trading_simulation.py | 54 ++++++++++++++++++++++++----- test_bitcoin_trading.py | 64 +++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 test_bitcoin_trading.py diff --git a/.Jules/palette.md b/.Jules/palette.md index 5de3a38..5189b91 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-23 - CLI Accessibility and Configuration +**Learning:** Hardcoded simulation parameters create friction and limit accessibility. Adding CLI arguments (`argparse`) empowers users to explore scenarios without code edits. Additionally, providing a `--no-color` flag is crucial for users with visual impairments or those piping output to logs. +**Action:** Always implement standard CLI flags for configuration and accessibility (quiet mode, no-color) in command-line tools. diff --git a/.gitignore b/.gitignore index d4fb281..03c45b7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,8 @@ # debug information files *.dwo + +# Python +__pycache__/ +*.py[cod] +*$py.class diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 82df43f..3557c3f 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 @@ -9,6 +10,15 @@ class Colors: ENDC = '\033[0m' BOLD = '\033[1m' + @classmethod + def disable(cls): + cls.HEADER = '' + cls.BLUE = '' + cls.GREEN = '' + cls.RED = '' + cls.ENDC = '' + cls.BOLD = '' + 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. @@ -49,7 +59,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 +69,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 +81,30 @@ 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__": +def main(days, initial_price, volatility, initial_cash, quiet, no_color): + if no_color: + Colors.disable() + # Simulate prices - prices = simulate_bitcoin_prices() + prices = simulate_bitcoin_prices(days=days, initial_price=initial_price, volatility=volatility) # Calculate moving averages signals = calculate_moving_averages(prices) @@ -96,11 +113,10 @@ def simulate_trading(signals, initial_cash=10000): signals = generate_trading_signals(signals) # Simulate trading - portfolio = simulate_trading(signals) + portfolio = simulate_trading(signals, initial_cash=initial_cash, quiet=quiet) # Final portfolio performance final_value = portfolio['total_value'].iloc[-1] - initial_cash = 10000 profit = final_value - initial_cash # Compare with buy and hold strategy @@ -118,3 +134,23 @@ def simulate_trading(signals, initial_cash=10000): print(f"Buy and Hold Strategy Value: ${buy_and_hold_value:.2f}") print(f"{Colors.HEADER}-----------------------------------------{Colors.ENDC}") + +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-cash', type=float, default=10000, help='Initial cash amount') + 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('--quiet', action='store_true', help='Suppress daily output') + parser.add_argument('--no-color', action='store_true', help='Disable colored output') + + args = parser.parse_args() + + main( + days=args.days, + initial_price=args.initial_price, + volatility=args.volatility, + initial_cash=args.initial_cash, + quiet=args.quiet, + no_color=args.no_color + ) diff --git a/test_bitcoin_trading.py b/test_bitcoin_trading.py new file mode 100644 index 0000000..0d4c256 --- /dev/null +++ b/test_bitcoin_trading.py @@ -0,0 +1,64 @@ +import unittest +from unittest.mock import patch +import pandas as pd +from bitcoin_trading_simulation import Colors, simulate_trading, main, simulate_bitcoin_prices, calculate_moving_averages, generate_trading_signals + +class TestBitcoinSimulation(unittest.TestCase): + + def tearDown(self): + # Reset Colors to default just in case + Colors.HEADER = '\033[95m' + Colors.BLUE = '\033[94m' + Colors.GREEN = '\033[92m' + Colors.RED = '\033[91m' + Colors.ENDC = '\033[0m' + Colors.BOLD = '\033[1m' + + def test_colors_disable(self): + # Disable colors + Colors.disable() + + # Verify empty strings + self.assertEqual(Colors.HEADER, '') + self.assertEqual(Colors.BLUE, '') + self.assertEqual(Colors.GREEN, '') + self.assertEqual(Colors.RED, '') + self.assertEqual(Colors.ENDC, '') + self.assertEqual(Colors.BOLD, '') + + @patch('builtins.print') + def test_simulate_trading_quiet(self, mock_print): + # Create dummy signals + prices = pd.Series([100, 101, 102], name='Price') + signals = pd.DataFrame(index=prices.index) + signals['price'] = prices + signals['positions'] = 0.0 + + simulate_trading(signals, quiet=True) + + # Verify print was not called + mock_print.assert_not_called() + + @patch('builtins.print') + def test_simulate_trading_verbose(self, mock_print): + # Create dummy signals + prices = pd.Series([100, 101, 102], name='Price') + signals = pd.DataFrame(index=prices.index) + signals['price'] = prices + signals['positions'] = 0.0 + + simulate_trading(signals, quiet=False) + + # Verify print was called (at least for the header) + self.assertTrue(mock_print.called) + + def test_main_runs(self): + # Run main with small number of days to ensure no errors + # We can suppress output with quiet=True + try: + main(days=5, initial_price=100, volatility=0.01, initial_cash=1000, quiet=True, no_color=True) + except Exception as e: + self.fail(f"main() raised {e} unexpectedly!") + +if __name__ == '__main__': + unittest.main() From 1b473de5655a819cd87eea2d24e91f4fe301ca44 Mon Sep 17 00:00:00 2001 From: EiJackGH Date: Sat, 14 Feb 2026 19:53:13 +0800 Subject: [PATCH 6/7] Add CodeQL analysis workflow configuration --- .github/workflows/codeql.yml | 101 +++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..36232fc --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,101 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '42 16 * * 6' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" From 071377452aa8e9f1983632f09bef2b0e8ffabde8 Mon Sep 17 00:00:00 2001 From: EiJackGH Date: Sun, 15 Feb 2026 00:21:38 +0800 Subject: [PATCH 7/7] Implement tests for Bitcoin price functions Add unit tests for Bitcoin price calculations and API handling. --- test_bitcoin.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test_bitcoin.py diff --git a/test_bitcoin.py b/test_bitcoin.py new file mode 100644 index 0000000..163248c --- /dev/null +++ b/test_bitcoin.py @@ -0,0 +1,35 @@ +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()