From 52901847665162fbb5a191b69ad8408344ea4786 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:32:41 +0000 Subject: [PATCH 1/2] Refactor `bitcoin_trading_simulation.py` to fix `Colors` duplication and redundant header printing. Enhance the final simulation report with an ASCII box layout, adding ROI, trade counts, and strategy comparison for better UX. Update tests to reflect changes. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 4 + ...bitcoin_trading_simulation.cpython-312.pyc | Bin 8406 -> 10895 bytes bitcoin_trading_simulation.py | 80 ++++++++++++------ test_bitcoin_trading.py | 6 +- 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 018831f..96bd45d 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -1,3 +1,7 @@ ## 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. + +## 2025-02-28 - Structured CLI Reports +**Learning:** Dense numerical data in CLI output is hard to parse. Using ASCII box-drawing characters and alignment to create a "dashboard" or "invoice" style summary significantly improves readability and perceived quality. +**Action:** When summarizing simulation or batch job results, always format the final report as a structured table or box rather than a list of print statements. diff --git a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc index 52abfc2edcf23a102e58b9d12c1192ee1c250d1b..7e31a40993555edc4fc2e9c1eabdcdd9113ecd64 100644 GIT binary patch delta 5333 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 ClV_s< delta 3146 zcmai0YfMzx9Y6QZonhV&=FN;SAdC*mOI>!?m0g7a6#)?hb$B^&FYLfP?3sZLBLuVS zwwT(;?%76&yJ0sqSsHCi>laL#Xx9&=X_LK^V!_c5nx<*}2Bq0enttg2+yO*u(i7(Y zKEMC}y#AMKZGSm3@kgCbjbOA~z8dobGT!dqYK zvw=^}+h6A+F(zBKk(*qXtW=hyju~ASA`e;ZcgJ3mT5LUcBAR~iy z?`y54>9Elu0=HK;GtjM<&XIXjD=IE|1_5Hmp`@Zc+}`d?=%UFMzeyk+pnC ztQb5-1qHto#Ci~|X8C53(nb{Ug_$IW_@W!!7wjueuaNtyUC86nP$xpzD<#_nzzsBq zmfsC70NKoB-Q*$?Oa2wD?NaTE+H$F8MPmV#lBaUx8mo}o z)@4XxlB5hAMS6Y+$lotoetGm+^0W8);A-DdX#FgsCnxkf-w26Rc^KKbQ#C;b26-6;#i{v2ZvP9Y}K#AsF>>*hdzP6@c?^W1jq7h$tetrUYZW zLriYgQV9n9CXfO6!C1YAo#cVJsH_TgtR)86%uZ;7 zjN_Fg-z{#bZYv{cn$4-_{U_;<|I#SA9G`D{29RQacal78;IA9DR})2 zjyH_Sd6_i9NWOKHW=FpULk7VlAz^t_m%@VK1>O{acQnE7=J%4>rM2fHHlav815MfhKB`mGpBeb zO^B<3Y@97RoaFq$QO__f@t|jQ_S1Q?mQ!fZZm8snr&S1tNx7?3L$4Noi;@Zwaus@M z45sta%1}bUIQBst*HD{^68dHIDRC9K<2qeHA4l344@HK<+pY^f1LpVO7x&7D&wW1s zGu!nYpX``2+*Ew6`ARcAF79pm)5y0M|8Q|JBI-MrlwD-r{mtP=X2%nw^>f>-d&WJj z|B|1pnAIuXw6&i630%V<|o+_FWKdKX_=wY+Kx&I@BtjJ|n(!Ry-RNdj+vS zlIo3$F)Rk-ONr!iVj`88SVbto9ASaEs@Z=bWUXOIb8aj1nB2@QoDffUiQNOs-9xGF zAu${gW5Z&2IMqEOevlNqF2bZSriRY-vyJTcl$~PNG7O9@Y%BG(u>s0jfJwd%s&%qG zl-023DKiJz?-0`Uva1!yT(o)sX{{n>!A4@s`$&~J=g9|$$xNPiLmT|ZVRA3en{8^3 z!EuDbp~R@CKiHRuVe(y`*BKW00Rekr!-)&S2_KtQfGZS>;!g60r?|Wb&I{8IINk-s z$KqxxpQ5Cmk{lpuMxFGPNW(*Z?QuklQ$8e;MILC-Mls;jeP(o?u8UV)(8>fupOFv@$5#lpO& z6?#!Z7#Mv4;wzcRr2qfaC&M<#5o&$_D}n#$V13WCfIZKZX# zX<2+Uo|Z?4BNs+74N5C6KqWk8?$0oABu&ln3&QME8JpdU` za}rfXV?BMba12Xd82s{F&A8dGCUG-o4O&Z|LsOLYJsN@le^&{cIy$u-2s#_j%t*hr?({iM<_%BX4E%g8Z diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index c86be3e..3e08606 100644 --- a/bitcoin_trading_simulation.py +++ b/bitcoin_trading_simulation.py @@ -7,31 +7,25 @@ class Colors: HEADER = '\033[95m' BLUE = '\033[94m' + CYAN = '\033[96m' GREEN = '\033[92m' - RED = '\033[91m' + WARNING = '\033[93m' + FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' + UNDERLINE = '\033[4m' @classmethod def disable(cls): cls.HEADER = '' cls.BLUE = '' + cls.CYAN = '' cls.GREEN = '' - cls.RED = '' + cls.WARNING = '' + cls.FAIL = '' cls.ENDC = '' cls.BOLD = '' - - -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' + cls.UNDERLINE = '' def simulate_bitcoin_prices(days=60, initial_price=50000, volatility=0.02): """ @@ -87,9 +81,7 @@ 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"\n{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}") + 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'] @@ -154,12 +146,48 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): 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"Final Portfolio Value: ${final_value:.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(f"{Colors.HEADER}-----------------------------------------{Colors.ENDC}") + # Calculate additional statistics + roi = (profit / initial_cash) * 100 + trade_count_buys = int(portfolio['btc'].diff().fillna(0).gt(0).sum()) + trade_count_sells = int(portfolio['btc'].diff().fillna(0).lt(0).sum()) + total_trades = trade_count_buys + trade_count_sells + vs_buy_hold = final_value - buy_and_hold_value + + # Format the final report + width = 44 + border = "═" * width + + print(f"\n{Colors.HEADER}{Colors.BOLD}╔{border}╗{Colors.ENDC}") + title = "Final Portfolio Performance" + print(f"{Colors.HEADER}{Colors.BOLD}║{title:^{width}}║{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}╠{border}╣{Colors.ENDC}") + + def print_line(label, value_str, color=Colors.ENDC): + print(f"{Colors.HEADER}{Colors.BOLD}║{Colors.ENDC} {label:<24}{color}{value_str:>18}{Colors.ENDC} {Colors.HEADER}{Colors.BOLD}║{Colors.ENDC}") + + print_line("Initial Cash:", f"${initial_cash:,.2f}") + print_line("Final Portfolio Value:", f"${final_value:,.2f}") + + profit_color = Colors.GREEN if profit >= 0 else Colors.FAIL + profit_sign = "+" if profit >= 0 else "-" + print_line("Profit/Loss:", f"{profit_sign}${abs(profit):,.2f}", profit_color) + + roi_color = Colors.GREEN if roi >= 0 else Colors.FAIL + roi_sign = "+" if roi >= 0 else "-" + print_line("ROI:", f"{roi_sign}{abs(roi):.2f}%", roi_color) + + print(f"{Colors.HEADER}{Colors.BOLD}╠{border}╣{Colors.ENDC}") + + print_line("Total Trades:", f"{total_trades}") + print_line(" - Buys:", f"{trade_count_buys}") + print_line(" - Sells:", f"{trade_count_sells}") + + print(f"{Colors.HEADER}{Colors.BOLD}╠{border}╣{Colors.ENDC}") + + print_line("Buy & Hold Value:", f"${buy_and_hold_value:,.2f}") + + vs_color = Colors.GREEN if vs_buy_hold >= 0 else Colors.FAIL + vs_sign = "+" if vs_buy_hold >= 0 else "-" + print_line("vs Buy & Hold:", f"{vs_sign}${abs(vs_buy_hold):,.2f}", vs_color) + + print(f"{Colors.HEADER}{Colors.BOLD}╚{border}╝{Colors.ENDC}") diff --git a/test_bitcoin_trading.py b/test_bitcoin_trading.py index e7eac6f..6c480ea 100644 --- a/test_bitcoin_trading.py +++ b/test_bitcoin_trading.py @@ -13,7 +13,7 @@ def reset_colors(): 'HEADER': Colors.HEADER, 'BLUE': Colors.BLUE, 'GREEN': Colors.GREEN, - 'RED': Colors.RED, + 'FAIL': Colors.FAIL, 'ENDC': Colors.ENDC, 'BOLD': Colors.BOLD, } @@ -22,7 +22,7 @@ def reset_colors(): Colors.HEADER = original_colors['HEADER'] Colors.BLUE = original_colors['BLUE'] Colors.GREEN = original_colors['GREEN'] - Colors.RED = original_colors['RED'] + Colors.FAIL = original_colors['FAIL'] Colors.ENDC = original_colors['ENDC'] Colors.BOLD = original_colors['BOLD'] @@ -58,7 +58,7 @@ def test_colors_disable(reset_colors): Colors.disable() assert Colors.HEADER == "" assert Colors.GREEN == "" - assert Colors.RED == "" + assert Colors.FAIL == "" def test_simulation_integration(): From 82c75dc9c666b5aad1d5de91d2dec12a9f706488 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:43:13 +0000 Subject: [PATCH 2/2] Fix CI failure by restoring missing `bitcoin.py` and `requests` dependency, and enhance CLI UX with ASCII dashboard. - Restore `bitcoin.py` with `get_bitcoin_price` and `calculate_value` functions to resolve `ModuleNotFoundError`. - Add `requests` to `requirements.txt`. - Fix Flake8 linting errors in `bitcoin_trading_simulation.py`, `test_bitcoin.py`, and `test_simulation.py` (whitespace, blank lines, imports). - Enhance `bitcoin_trading_simulation.py` final report with an ASCII box layout, ROI, and trade stats. - Consolidate `Colors` class in `bitcoin_trading_simulation.py` and remove duplicate `argparse` import. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- ...bitcoin_trading_simulation.cpython-312.pyc | Bin 10895 -> 10887 bytes bitcoin.py | 24 ++++++++++++++++++ bitcoin_trading_simulation.py | 8 +++--- requirements.txt | 1 + test_bitcoin.py | 6 ++++- test_simulation.py | 4 ++- 6 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 bitcoin.py diff --git a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc index 7e31a40993555edc4fc2e9c1eabdcdd9113ecd64..81dd6550442bb097e35393c0939f196966f2f006 100644 GIT binary patch delta 482 zcmeAVZ4c!=&CAQh00bS$GcrF-HqYUX5ETTeVFu#Q zN{kE)(-}$_CM)vGu*?QA0(fNDW;3KP0-5@Xa%^*1YMF6Ms{y51N;p9_fQK%g#)fox#6`Z9Vr&?gh0QocFNp=ibS^ zq4t89YX|ooDTVpEGj(UkuMt}>y;6Dw?}p$#V*90cO7GykAnw+|bBBX>g7gI58G$oO z7X;79UZJ`|e1qf;;|=^r*p71_QV z7}yNOlYyT9z|6?Vc$qT#a&5k^v5b)` NnTdf#sz?~96##t-iZ}oO delta 470 zcmZutOG^S#6h8MjjWyF`83tB{y&sGILWC_05`qeRP(v^-EVY<%RZ1;_mL(JR7+M6q zwqcO%T3T&J5QOYUBo%Etm$n@^-*@i)zH>R}?k^oIDX)qm0Yz~2B=MM6yv)vz`kFP+ zSQ4UeH?t=(xA`o+FlX^d4tsjq$?Kr}bTfcs$PoTT22Edo9}-RV_X!%K;;gl|%K{Cv zpgAldixy;=CRoCsBy#EhF2REHHq$jQ)V)ZL>jDQShc*rh!MMW=4S079K8oM(*E@*c zq)k1M&Zx;mIEZCD?`cX62~s*q8Smx-P{V`9eO)-3IQ(2l-CE&6TE0@uxs3 zR`!&g_I$*SE|+(zYP_bdRn#?mW!+v+R#QeTwN*)N*~#rMfE)|p55RPe1+l@8LHq#P zZkE$5&LMe1xWGLj408)7(&jM2$rZ(KfR-2*y@L^CY10_qm?+Ss{5y>TE&SnNjMc$a o2X`GhsrV5+tfv74pOeLwkur*`DTWQPSR1=$l8PX7>z&m73m1cYk^lez diff --git a/bitcoin.py b/bitcoin.py new file mode 100644 index 0000000..03b3371 --- /dev/null +++ b/bitcoin.py @@ -0,0 +1,24 @@ +import requests + + +def get_bitcoin_price(): + """ + Fetches the current Bitcoin price from CoinDesk API. + """ + url = "https://api.coindesk.com/v1/bpi/currentprice.json" + try: + response = requests.get(url) + if response.status_code == 200: + data = response.json() + return data["bpi"]["USD"]["rate_float"] + else: + raise ConnectionError(f"API returned status code {response.status_code}") + except requests.RequestException: + raise ConnectionError("Failed to fetch Bitcoin price") + + +def calculate_value(amount, price): + """ + Calculates the total value of Bitcoin based on the amount and current price. + """ + return float(amount) * float(price) diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index 3e08606..001e336 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: @@ -27,6 +26,7 @@ def disable(cls): cls.BOLD = '' cls.UNDERLINE = '' + 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. @@ -145,7 +145,7 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): # Compare with buy and hold strategy buy_and_hold_btc = args.initial_cash / prices.iloc[0] buy_and_hold_value = buy_and_hold_btc * prices.iloc[-1] - + # Calculate additional statistics roi = (profit / initial_cash) * 100 trade_count_buys = int(portfolio['btc'].diff().fillna(0).gt(0).sum()) @@ -163,7 +163,9 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): print(f"{Colors.HEADER}{Colors.BOLD}╠{border}╣{Colors.ENDC}") def print_line(label, value_str, color=Colors.ENDC): - print(f"{Colors.HEADER}{Colors.BOLD}║{Colors.ENDC} {label:<24}{color}{value_str:>18}{Colors.ENDC} {Colors.HEADER}{Colors.BOLD}║{Colors.ENDC}") + left_border = f"{Colors.HEADER}{Colors.BOLD}║{Colors.ENDC}" + right_border = f"{Colors.HEADER}{Colors.BOLD}║{Colors.ENDC}" + print(f"{left_border} {label:<24}{color}{value_str:>18}{Colors.ENDC} {right_border}") print_line("Initial Cash:", f"${initial_cash:,.2f}") print_line("Final Portfolio Value:", f"${final_value:,.2f}") diff --git a/requirements.txt b/requirements.txt index 5da331c..4ad1501 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ numpy pandas +requests diff --git a/test_bitcoin.py b/test_bitcoin.py index 163248c..4785f33 100644 --- a/test_bitcoin.py +++ b/test_bitcoin.py @@ -2,6 +2,7 @@ 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.""" @@ -10,10 +11,12 @@ def test_calculate_value(): 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): @@ -23,10 +26,11 @@ def test_get_bitcoin_price(mock_get): "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): diff --git a/test_simulation.py b/test_simulation.py index 0f4f1f8..8fddb57 100644 --- a/test_simulation.py +++ b/test_simulation.py @@ -1,8 +1,8 @@ -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) @@ -10,6 +10,7 @@ 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') signals = calculate_moving_averages(prices, short_window=3, long_window=5) @@ -17,6 +18,7 @@ def test_calculate_moving_averages(): assert 'long_mavg' in signals.columns assert not signals['short_mavg'].isnull().all() + def test_generate_trading_signals(): # Create dummy signals DataFrame data = {