From 27ddf6ddc3e7a377af76a12f96565472d717d6a1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:09:15 +0000 Subject: [PATCH 1/2] feat: add CLI arguments for customization and accessibility Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 ++++ README.md | 22 +++++++++++++++++-- bitcoin_trading_simulation.py | 41 ++++++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 5de3a38..bcac195 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 & Pipeability +**Learning:** Hardcoded ANSI color codes in CLI tools break accessibility for screen readers and make piping output to files messy. Users need a way to disable them. +**Action:** Always implement a `--no-color` flag in CLI tools that programmatically disables all color codes. diff --git a/README.md b/README.md index be561ef..acb275d 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. - **Performance metrics:** Compares the strategy's performance against a "Buy and Hold" approach. +- **Customizable:** Configure simulation parameters via CLI arguments. ## Installation @@ -20,12 +21,29 @@ 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 (default: 10000) +- `--initial-price`: Initial Bitcoin price (default: 50000) +- `--volatility`: Volatility factor (default: 0.02) +- `--quiet`: Suppress daily ledger output (only shows trades and final report) +- `--no-color`: Disable colored output (useful for logs or accessibility) + +Example: + +```bash +python bitcoin_trading_simulation.py --days 100 --initial-cash 5000 --quiet +``` + ## Tests Run the test suite: diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 82df43f..6cf2dfa 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,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,13 +93,30 @@ 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}, Cash: ${portfolio.loc[i, 'cash']:.2f}, BTC: {portfolio.loc[i, 'btc']:.4f}") return portfolio +def parse_arguments(): + parser = argparse.ArgumentParser(description='Bitcoin Trading Simulation') + parser.add_argument('--days', type=int, default=60, help='Number of days to simulate (default: 60)') + parser.add_argument('--initial-cash', type=float, default=10000, help='Initial cash (default: 10000)') + parser.add_argument('--initial-price', type=float, default=50000, help='Initial Bitcoin price (default: 50000)') + parser.add_argument('--volatility', type=float, default=0.02, help='Volatility factor (default: 0.02)') + parser.add_argument('--quiet', action='store_true', help='Suppress daily ledger output') + parser.add_argument('--no-color', action='store_true', help='Disable colored output') + return parser.parse_args() + if __name__ == "__main__": + args = parse_arguments() + + 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 +125,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 From f9bd002d96428559af40c9c5247efc76de0b906c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:28:23 +0000 Subject: [PATCH 2/2] fix: linting errors, test discovery, and gitignore Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .gitignore | 2 + bitcoin_trading_simulation.py | 115 ++++++++++++++++++++++++---------- test.py | 3 - test_bitcoin_trading.py | 52 +++++++++++++++ 4 files changed, 136 insertions(+), 36 deletions(-) delete mode 100644 test.py create mode 100644 test_bitcoin_trading.py diff --git a/.gitignore b/.gitignore index d4fb281..fe1bf1a 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ # debug information files *.dwo +__pycache__/ +*.pyc diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 6cf2dfa..1f0a55d 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -2,6 +2,7 @@ import numpy as np import pandas as pd + class Colors: HEADER = '\033[95m' BLUE = '\033[94m' @@ -19,9 +20,11 @@ def disable(cls): 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. + Simulates Bitcoin prices for a given number of days using + Geometric Brownian Motion. """ dt = 1 prices = [initial_price] @@ -33,32 +36,42 @@ 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. """ signals = pd.DataFrame(index=prices.index) signals['price'] = prices - signals['short_mavg'] = prices.rolling(window=short_window, min_periods=1, center=False).mean() - signals['long_mavg'] = prices.rolling(window=long_window, min_periods=1, center=False).mean() + signals['short_mavg'] = prices.rolling( + window=short_window, min_periods=1, center=False).mean() + 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. - A buy signal (1.0) is generated when the short moving average crosses above the long moving average. - A sell signal (-1.0) is generated when the short moving average crosses below the long moving average. + A buy signal (1.0) is generated when the short moving average + crosses above the long moving average. + A sell signal (-1.0) is generated when the short moving average + crosses below the long moving average. """ signals['signal'] = 0.0 # A Golden Cross (buy signal) - signals.loc[signals['short_mavg'] > signals['long_mavg'], 'signal'] = 1.0 + 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.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, quiet=False): """ Simulates trading based on signals and prints a daily ledger. @@ -70,45 +83,69 @@ 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"{Colors.HEADER}{Colors.BOLD}" + f"------ Daily Trading Ledger ------" + f"{Colors.ENDC}") for i, row in signals.iterrows(): if i > 0: - portfolio.loc[i, 'cash'] = portfolio.loc[i-1, 'cash'] - portfolio.loc[i, 'btc'] = portfolio.loc[i-1, 'btc'] + portfolio.loc[i, 'cash'] = portfolio.loc[i - 1, 'cash'] + portfolio.loc[i, 'btc'] = portfolio.loc[i - 1, 'btc'] # Buy signal if row['positions'] == 2.0: 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}") + print(f"{Colors.GREEN}Day {i}: 💰 Buy {btc_to_buy:.4f} BTC " + f"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}") + print(f"{Colors.RED}Day {i}: 📉 Sell " + f"{portfolio.loc[i, 'btc']:.4f} " + f"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'] + portfolio.loc[i, 'total_value'] = ( + portfolio.loc[i, 'cash'] + portfolio.loc[i, 'btc'] * row['price'] + ) 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}") - + print(f"Day {i}: Portfolio Value: " + f"${portfolio.loc[i, 'total_value']:.2f}, " + f"Cash: ${portfolio.loc[i, 'cash']:.2f}, " + f"BTC: {portfolio.loc[i, 'btc']:.4f}") + return portfolio + def parse_arguments(): parser = argparse.ArgumentParser(description='Bitcoin Trading Simulation') - parser.add_argument('--days', type=int, default=60, help='Number of days to simulate (default: 60)') - parser.add_argument('--initial-cash', type=float, default=10000, help='Initial cash (default: 10000)') - parser.add_argument('--initial-price', type=float, default=50000, help='Initial Bitcoin price (default: 50000)') - parser.add_argument('--volatility', type=float, default=0.02, help='Volatility factor (default: 0.02)') - parser.add_argument('--quiet', action='store_true', help='Suppress daily ledger output') - parser.add_argument('--no-color', action='store_true', help='Disable colored output') + parser.add_argument( + '--days', type=int, default=60, + help='Number of days to simulate (default: 60)') + parser.add_argument( + '--initial-cash', type=float, default=10000, + help='Initial cash (default: 10000)') + parser.add_argument( + '--initial-price', type=float, default=50000, + help='Initial Bitcoin price (default: 50000)') + parser.add_argument( + '--volatility', type=float, default=0.02, + help='Volatility factor (default: 0.02)') + parser.add_argument( + '--quiet', action='store_true', + help='Suppress daily ledger output') + parser.add_argument( + '--no-color', action='store_true', + help='Disable colored output') return parser.parse_args() + if __name__ == "__main__": args = parse_arguments() @@ -116,27 +153,35 @@ def parse_arguments(): Colors.disable() # Simulate prices - prices = simulate_bitcoin_prices(days=args.days, initial_price=args.initial_price, volatility=args.volatility) - + 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, initial_cash=args.initial_cash, quiet=args.quiet) - + portfolio = simulate_trading( + signals, initial_cash=args.initial_cash, quiet=args.quiet + ) + # Final portfolio performance final_value = portfolio['total_value'].iloc[-1] 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"\n{Colors.HEADER}{Colors.BOLD}" + f"------ Final Portfolio Performance ------" + f"{Colors.ENDC}") print(f"Initial Cash: ${initial_cash:.2f}") print(f"Final Portfolio Value: ${final_value:.2f}") @@ -146,4 +191,8 @@ def parse_arguments(): print(f"Profit/Loss: {Colors.RED}📉 ${profit:.2f}{Colors.ENDC}") print(f"Buy and Hold Strategy Value: ${buy_and_hold_value:.2f}") - print(f"{Colors.HEADER}-----------------------------------------{Colors.ENDC}") + print( + f"{Colors.HEADER}" + f"-----------------------------------------" + f"{Colors.ENDC}" + ) 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..8323bc3 --- /dev/null +++ b/test_bitcoin_trading.py @@ -0,0 +1,52 @@ +# Filename: test_bitcoin_trading.py +from bitcoin_trading_simulation import ( + simulate_bitcoin_prices, + calculate_moving_averages, + generate_trading_signals, + simulate_trading +) +import pandas as pd + + +def test_simulate_bitcoin_prices(): + prices = simulate_bitcoin_prices(days=10, initial_price=100) + assert len(prices) == 10 + assert prices.iloc[0] == 100 + + +def test_calculate_moving_averages(): + prices = pd.Series([10, 11, 12, 13, 14, 15, 16, 17, 18, 19], name='Price') + signals = calculate_moving_averages(prices, short_window=2, long_window=5) + assert 'short_mavg' in signals.columns + assert 'long_mavg' in signals.columns + + +def test_generate_trading_signals(): + # Create a dummy signals DataFrame + data = { + 'price': [100, 105, 110, 100, 90], + 'short_mavg': [100, 105, 110, 100, 90], + 'long_mavg': [100, 100, 100, 105, 110] + } + signals = pd.DataFrame(data) + signals = generate_trading_signals(signals) + assert 'signal' in signals.columns + assert 'positions' in signals.columns + # Check if signals are generated (1.0 for buy, -1.0 for sell) + assert signals['signal'].iloc[2] == 1.0 # Buy signal + assert signals['signal'].iloc[4] == -1.0 # Sell signal + + +def test_simulate_trading(): + # Create a dummy signals DataFrame with a buy signal + data = { + 'price': [100, 105, 110], + 'short_mavg': [100, 105, 110], + 'long_mavg': [100, 100, 100], + 'positions': [0.0, 2.0, 0.0] # Buy at index 1 + } + signals = pd.DataFrame(data) + portfolio = simulate_trading(signals, initial_cash=10000, quiet=True) + assert not portfolio.empty + assert portfolio['btc'].iloc[1] > 0 + assert portfolio['cash'].iloc[1] < 10000