diff --git a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc index 52abfc2..88f28e6 100644 Binary files a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc and b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc differ diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index ccb5edd..b9636d3 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -1,7 +1,6 @@ import argparse import numpy as np import pandas as pd -import argparse class Colors: @@ -24,7 +23,7 @@ def disable(cls): 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 GBM. """ dt = 1 prices = [initial_price] @@ -43,16 +42,16 @@ def calculate_moving_averages(prices, short_window=7, long_window=30): """ 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. """ signals['signal'] = 0.0 # A Golden Cross (buy signal) @@ -60,7 +59,7 @@ def generate_trading_signals(signals): # 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 + # Create 'positions': 1 for buy, -1 for sell, 0 for hold signals['positions'] = signals['signal'].diff().shift(1) return signals @@ -69,62 +68,75 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): """ Simulates trading based on signals and prints a daily ledger. """ - portfolio = pd.DataFrame(index=signals.index).fillna(0.0) - portfolio['price'] = signals['price'] - portfolio['cash'] = float(initial_cash) - portfolio['btc'] = 0.0 - portfolio['total_value'] = float(initial_cash) + # Use lists to store history for performance optimization + # Iterating over numpy arrays is faster than DataFrame.loc access + cash_history = [] + btc_history = [] + total_value_history = [] + + current_cash = float(initial_cash) + current_btc = 0.0 + + # Extract data to numpy arrays for faster iteration + prices = signals['price'].values + positions = signals['positions'].fillna(0.0).values if not quiet: - print(f"{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}" + f"------ Daily Trading Ledger ------{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'] + for i in range(len(signals)): + price = prices[i] + position = positions[i] # 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'] + if position == 2.0: + btc_to_buy = current_cash / price + current_btc += btc_to_buy + current_cash -= btc_to_buy * price if not quiet: - 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} " + f"BTC at ${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 + elif position == -2.0: + if current_btc > 0: + cash_received = current_btc * price + current_cash += cash_received 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 + print(f"{Colors.RED}Day {i}: 📉 Sell {current_btc:.4f} " + f"BTC at ${price:.2f}{Colors.ENDC}") + current_btc = 0.0 - portfolio.loc[i, 'total_value'] = portfolio.loc[i, 'cash'] + portfolio.loc[i, 'btc'] * row['price'] + # Calculate total value + total_value = current_cash + current_btc * price - 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}") + # Store history + cash_history.append(current_cash) + btc_history.append(current_btc) + total_value_history.append(total_value) - return portfolio + if not quiet: + print(f"Day {i}: Portfolio Value: ${total_value:.2f}, " + f"Cash: ${current_cash:.2f}, BTC: {current_btc:.4f}") + # Create portfolio DataFrame from lists + portfolio = pd.DataFrame(index=signals.index) + portfolio['price'] = signals['price'] + portfolio['cash'] = cash_history + portfolio['btc'] = btc_history + portfolio['total_value'] = total_value_history -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") + return portfolio - args = parser.parse_args() - if args.no_color: +def main(days, initial_cash, initial_price, volatility, quiet, no_color): + if no_color: Colors.disable() # Simulate prices - prices = simulate_bitcoin_prices(days=args.days, initial_price=args.initial_price, volatility=args.volatility) + prices = simulate_bitcoin_prices( + days=days, initial_price=initial_price, volatility=volatility) # Calculate moving averages signals = calculate_moving_averages(prices) @@ -133,19 +145,20 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): 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=initial_cash, quiet=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 = args.initial_cash / prices.iloc[0] + 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: ${args.initial_cash:.2f}") + print(f"\n{Colors.HEADER}{Colors.BOLD}" + f"------ Final Portfolio Performance ------{Colors.ENDC}") + print(f"Initial Cash: ${initial_cash:.2f}") print(f"Final Portfolio Value: ${final_value:.2f}") if profit >= 0: @@ -154,24 +167,32 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): 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"{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') + 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() main( days=args.days, + initial_cash=args.initial_cash, 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.py b/test_bitcoin.py deleted file mode 100644 index 163248c..0000000 --- a/test_bitcoin.py +++ /dev/null @@ -1,35 +0,0 @@ -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() diff --git a/test_simulation.py b/test_simulation.py index 0f4f1f8..147aadb 100644 --- a/test_simulation.py +++ b/test_simulation.py @@ -1,7 +1,11 @@ -import pytest import pandas as pd import numpy as np -from bitcoin_trading_simulation import simulate_bitcoin_prices, calculate_moving_averages, generate_trading_signals +from bitcoin_trading_simulation import ( + simulate_bitcoin_prices, + calculate_moving_averages, + generate_trading_signals +) + def test_simulate_bitcoin_prices(): days = 10 @@ -10,19 +14,23 @@ def test_simulate_bitcoin_prices(): 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') + 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] + 'long_mavg': [100, 100, 100, 103, 105] } signals = pd.DataFrame(data) signals = generate_trading_signals(signals)