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 = {