From d5c7f575c33cbe57f8c798dadaf03ed0b0f66c1c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:12:37 +0000 Subject: [PATCH 1/2] Enhance CLI output with ASCII dashboard and fix quiet mode - Consolidated duplicate `Colors` class definitions into a single, comprehensive class. - Fixed `simulate_trading` to properly respect the `--quiet` flag, suppressing all intermediate logs. - Enhanced the final performance report with an ASCII box layout, padded alignment, and additional statistics (ROI, Total Trades). Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- .Jules/palette.md | 6 +- ...bitcoin_trading_simulation.cpython-312.pyc | Bin 8406 -> 11440 bytes bitcoin_trading_simulation.py | 59 +++++++++++------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 018831f..d9b8b02 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -1,3 +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. +## 2024-05-22 - [CLI Dashboard Pattern] +**Learning:** Simple CLI outputs can be significantly improved by using ASCII box-drawing characters and padded alignment to create a "dashboard" feel. Users perceive this as more polished and professional than raw text streams. Also, ensure `--quiet` flags silence *all* intermediate output to respect user intent. +**Action:** Use Python f-string alignment (`<`, `>`, `^`) and box-drawing characters for final summary reports in CLI tools. diff --git a/__pycache__/bitcoin_trading_simulation.cpython-312.pyc b/__pycache__/bitcoin_trading_simulation.cpython-312.pyc index 52abfc2edcf23a102e58b9d12c1192ee1c250d1b..dce400ca1fdf9e23e9d33acd4e3a3e193a1432b8 100644 GIT binary patch literal 11440 zcmd5?eQX;?c3&=+-=su+QZgxLCCip*Tb5-zj=wLGD9N^LMV2gEqU57j+?7O`Us{r~ zMN2u}A;8IP+Q@AVrke{yw*@RCMne|`PKOqVul?f;^q<{G2Qqv9!^ss0{GU^&J)kJi zzM17xAC{ctd__8rW_D)Yy!YnKn>W81=3gxqBLS(}bj$bOTM6QKsG_98+{bf0(A*+e zf>rqmw_K{+B(AI7l+sQT9Ry3hL$K4ZQC~Jb6 zfitq^EA<31NAXJ}!4c!-is+S+wcIcGNR{Jf=FH>vl|JQ*E#wMVinZP^`iPR-RKzBf z#atn4WJ|sUWhqc<*s^aySq_w1*7hwZE7(eQ%YFMt>Tl4}0d!UC=&F_3u9P3;KHWuJ z(Rl4jyYj_bIn8*TQd=pNaVi3=u7$C%2I~{F?p)tS5YW2&#n$^?~agB;$u(q+_d5kGpDP-tJ1SimQx+C%do?_0 zy&`Alt8!*{ZpMM##Z|JqKcLtKpxDron~(j_@a^WcIR|zRSM|q`&TVChD?-Ye`|}%l1=|R`!9JVX?+>w?`IYU4q;9P5UhXxv3D$GpA5~+^ipIeQuVCW~8`^mP z|63bxtndH1jbCcv&7*;OWtCpaVe@EkfUAZW`8rqg0r8=lJ@~!Fg*UkEe~h^BGAqFT z*!YAvwAs%2a8(?iQcbCx$DSg0?((j7>Kxu9p1Yv+79csdiX{Lcs@!Up1caytBuD`Y z)Bpn1y7ipiZD4h7Bdd3tScBWlSypTjz`s%gT5J5Yved1_o!<|Oy!5L=Ei zhca_Dzz}l6`Nqe%b+11t~Q7vcs+9axVpt(!Zbo8{f zbxFF5%{^VGx;iAPz4_D`N!`=tlBl*WS1S;|b;czb`nq7`nNwYDPL-tg`XdoEANs#) z8kumoao892hI~O!lxJ99aMTm=1!8_C>I((;gr_7O>x(eMelA`h8+}i%UIH8UWLVk}@dtN>N%4HAA+%0t%DTSX>GA{xi#SH03U5p``bCf=qz( zcqF6869};}Kl(O%JQFd-FZb}sc04i}k1WNbiwX}&iQvsBv!JX1W$2#5TOpH7UOy9w z1i0u}h~<&;_Xxfe^%kQHts2e{<48MX?+{swEzzDel_gwqW-d>(WeX}29dgxJkZ8@C zN>MM$td)sVPc+uANTbI7Tuo?;@RuwCaesOYW!?Wm5MN#|{kx9O?NGq_z#UVq+Z!exd`1N`!5~leO{=Jgm{{vM}3oA zkPgNI!yHeCMrf9qiqNqL_&43bg}@Dfm~P=iSA#w#NS_U1cgKvMB75`5oKOD;lBDTI zd|~5I$oHIjNy9V2QBER*VMz<0Y$zaUgCRb^_$4Yb!AB)6>?$tEN@SRow7nef<04Lj zL?IOWXo+^&ZF#SnH9eu(ZXNk24o51nVvA35-r&R`>ZF?{@7AHcOY4P^U$Ar zW>rtDMYEd6*5X@+n}&>)7OixuD0O40_v_n%g= zYV2A$Hy_W3Ttx;rsv=FzYG1;M{7#&7n3Eb@Ssy=(J+77Udr`y%=Y_@Qqck`b?GJ&g z211j_W0^^gXJFaa_%>c%I)dG659V`EC24#?mb)fta3>G1HblEqEorYpbLgsM3P6|- z!-xL%;czKKvMCiQ^VK8XvyGWqD(svp3fgdbsZn_`#~p(3UEx& zN%4D7t6Aouh{0TP6D`Z-g$skpt|iA%#=(dVX360N zk~1PznYCBXpSXP@ZGTiPoF7P@ShBk_c8_THEZG?#=@hAotf@HBCA(xk+Jf-BOa3!d zP)vphj9rQa_Zo+`^$GeZ97PFMgUTx|6lX`|G1_lA2Hag4gXAGT<2ff?El+XIairzC z#Ojod037tP>w?1>7&}Z4`$`8F1fN1Kmpk_9+&a<2OoU_UP>_y}adbz>&q5q+c# zQ!403cG4kG85o_4gTT4sf~k| zdDY&RYYqHYMocSGz$@dnRinws=PT>IYON|>TnAW9%QOXf!L*hwgloQ4;mQ_$K&=|P z@jB}jzf}>zyD(C=QZaf8=I38FZ){C@r8l>xvO25g34nbwYp%A9ZjCj`$jJ@uv1LPh zlwf<5ZYn3I)vH;HHN1@fOFdWdvCZtTfz{CEV^ewBoR5<{Ij4H}(Tb(mGHi+RmyN{h z2%f^)Hl@nxi~NbL*pzCORX<$iRdeLKMHJ``hj?YY1E1h47a-*X&!61NQI2pVAi-c1 z&RMtt`TSF~pJPWkUOrg)*TEiq8_M2*OrpF@WK2>IN4=6M8j8XtWs>p7I7wxU*EHZn zyD*dU@=F1I1~a4OK73`uXCmJL+`fcAl-`V?!2N7~{;y&16dl(dp<4hq#MOHajKmFe z%lTHCiPE)D-#4O+d6*mX{y)fLbVul31Oeg{W>I>C?gp4V67u^(^aZ5i5ny20O}B#n z=u@{F@jXI23owY~kAns>eASNl{P2b#X-51Z1}<0FI`Zvbhlucd0lJ1tdLMvexF1IN zlc)o~FrWj&smCX}WL7S6*l3!Gn2(DhFp$$rQu!n`43~^xPER!C0k9-l5Uq#jyd2z4 z*$BE;BSegnj18>$OI&VEdGrp2a6v>3CocxWMJ2b)H_fRx?jFB$eA(KRu^tkwhZakg ztj80lvXykoCRREVr)Eb*s{C<9Rnosyu?wmr03x!sE%3zwYJU7$ZE9%gwS74T`n%$o@nP(^d9SudKm0ZK6!&R!Qy_GIK!Q%$zAX1?lnRcc~k zM`}u}Y)ZE->K>dCE8mvmJl|R2QKX`jE zd3K>P-IU&zzPLE>$)#UjderoYdvr)VeqQM77mf`G1H*!ML>LW<-jEQE2*c6k*!ABN z#H6a3{7NpK5kySYLPBA0CBG(!G8dWMxoggl6J3@Z6RmYmcJBYU;aSbgux= z)Aq3P*Hyo&dNd*QTzqs*Z0;8ZhJ-gSJyWYnJgWaBRF=!CXZwkg?avMo<}!gQ#g{bn zL>OS;F6)VJQC@$Pn3M~P0LbCV#>8?FFYgN@d>+>}G@vJ#_?E7DO8G% za}F2;)c2}7818Rk=spDs#VcvWSz7TSrYMU}vlC7Gcf;u@sc0aRCzw0+ z@>Fzf4E@h9&*uLkO?UI55nr_N3|z2}Ufq z$mY>qPbz5UM`HnaX6r^=cr+B3W`<=wz#R8UY5-__AEGzne$*3@D4gqqArIcT_$P~X zTNb~)VKlO8>$(vm%7-2zOe1JBRNVmHc~ofvV(rjZ;i7i0uotA3N_kA0EiG_KB^1!th9DctRYW zShnzqb|5IMU(hZUG$uOGcU|hL@?BJ&vZbS$H%^LgoD{mxXS&^Dw|m(-m^cO4q{NXt zD;Dp}6gP^+jcHOW-k<2qme?{SwPHza;!M_3kvt|^>dfF`jbAB5k-N^HsPH;*OG zfZPg4##S%d>K6`veDvPY^u)arqU|so+1X0xLi@*^_c|BJdvA%AM+K_$wGwN13<~G^1sC8sw=l?L2FJv~F~RK;&@E( zO$w7&g==raWT#Z^XtJn)$zD@+prV63jfzh49I{6zc>$520vb<~7g15zkL+O>Aiq(8 z3&KS-8{xvRFzgjpqv!C1z+cPoKN9&L0Y@IdDiqD63l(i-Cz9Ppb|cxSfJPT61Vv#F z`5z#=m;8Lw$=e^;(-z@4asc7-sL($q_(B<9RP;rKv6v8>6t2zy_iL&)#2ppD_Nr`y zcJdUeod%IWa+>TzB&dMKQ?d;TFXGAfzhv?`1>ov{7od+37(mXxj2AF^P#6vgp|CJ< zH8b(HIPta+M)QA@Y(*kmV0)13BHu!CQ2~vu^85?WVYxjd+Auk9hhE2JM8lAyXdpxS z(Lhu{<7EP{0E4Dt`34ec@K7AhOu{MbQ#&g zZf%0iHl=&F5oyb#JCn(PVDw-6Ao?RWLVuyg#?Y20>%Za zX(qxbHXBhXnv1AebQp>3H6*H**wV^~M%*rHt}zj zor-KDe>S+8Gvr@}v#rx8$o3XdP*c<#={ywf>@RX1-KRQ>>cjn=oxMd>e`mPJb@qls z;6KqFCJ&9r+AASTlJ2KD^w{a1SAAe8*qxws1&0fizF>2J(r!2@_I=$tEv+|bB=1yo z%odHKF(H|ZOR2XLA~pl^GlVUmFy+SdtSLeRljJTC*HFRap6eNiS;t;*;MYKm!w<&VJ?tf0oQ8TY5)^ppm9;yT1=iP2j} zhBzZR;cjDeLWr<&ZavXhU!quLUIje?d}aI~56hRxy}zru^3tOw;1U*z~7{zjpY3e0UCoTZBZ9ZNf;-tByouP^iUs()C$VE&2br^Y+R#dqcHy}!Tw$E&}+`XDY>o?Fok z7l^k;){5D-?3U~hS*!WpiO9{!Vt1Cyp3H`_Gr6PpdhYf-sC#hgK~sMJfIRq?{Kgsi zj38f>d0h%WKxdBVPcRKx$3K^1mEifG^G2#sL)j|v|9sTS1J8f6}} zKr6a}O+}r6$1e#uDHT=8>10tom5yJU#x$s?xdf#MNvv#m<^H7?Bc_*{atmR)k+GE= zXl!MANTQJs(pww9*lir7K6<@9*Y$pqYpf;zZ4AuzQh?TlH;>WR=LS0|V4;G31B$x+ z@r0O;NpE5dCcQ0_@4_cMr43CiQ5wd^9nHkqJ1Y*6ug4wq2*D>~gI4T+Y zIv-%1YkdsjD&{j^sJsPoC-C|k<8rqkbM6)HVu3TPa_&6mmOa5`b?%DXBPxKj2`pA- zPvye$;fUfNBF?RM%_h&{o>kxWJp7rq&vlWOrqHb139Lu)crwOPy)Kv^aJ< zb~Cm(lHI!;$k~_uxv_hbcPDeha^>-dy6z|CkcpcUi|4YgWzBLjJ99MGb?^AyD#38 0: portfolio.loc[i, 'cash'] = portfolio.loc[i-1, 'cash'] @@ -100,14 +95,16 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): 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.FAIL}🔴 Day {i}: Sell {portfolio.loc[i, 'btc']:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}") + if not quiet: + 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'] @@ -149,17 +146,31 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): final_value = portfolio['total_value'].iloc[-1] initial_cash = args.initial_cash profit = final_value - initial_cash + roi = (profit / initial_cash) * 100 + + # Calculate trade stats + buys = len(signals[signals['positions'] == 2.0]) + sells = len(signals[signals['positions'] == -2.0]) + total_trades = buys + sells # 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] - 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"\n{Colors.HEADER}{Colors.BOLD}╔══════════════════════════════════════════╗{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}║ Final Portfolio Performance ║{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}╠══════════════════════════════════════════╣{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {'Initial Cash:':<25} ${initial_cash:>14,.2f} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {'Final Value:':<25} ${final_value:>14,.2f} {Colors.HEADER}║{Colors.ENDC}") + if profit >= 0: - print(f"{Colors.GREEN}💰 Profit/Loss: ${profit:.2f}{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.GREEN}💰 Profit/Loss: ${profit:>14,.2f}{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.GREEN} ROI: {roi:>14.2f}%{Colors.ENDC} {Colors.HEADER}║{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}") + print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.FAIL}📉 Profit/Loss: ${profit:>14,.2f}{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.FAIL} ROI: {roi:>14.2f}%{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") + + print(f"{Colors.HEADER}╠══════════════════════════════════════════╣{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {'Trades (B/S):':<25} {f'{buys}/{sells}':>15} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {'Buy & Hold:':<25} ${buy_and_hold_value:>14,.2f} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}╚══════════════════════════════════════════╝{Colors.ENDC}") From 4df15bdba61a0e96f7325f5de63589887a2194e0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:23:34 +0000 Subject: [PATCH 2/2] Fix CI linting errors and missing module - Fix flake8 errors in `bitcoin_trading_simulation.py` (line length, imports, spacing). - Create `bitcoin.py` module required by `test_bitcoin.py` (implementation of `get_bitcoin_price` and `calculate_value`). - Add `requests` to `requirements.txt`. - Fix linting errors in `test_bitcoin.py` and `test_simulation.py`. Co-authored-by: EiJackGH <172181576+EiJackGH@users.noreply.github.com> --- ...bitcoin_trading_simulation.cpython-312.pyc | Bin 11440 -> 11249 bytes bitcoin.py | 22 ++++++++++++ bitcoin_trading_simulation.py | 34 +++++++++++------- requirements.txt | 1 + test_bitcoin.py | 6 +++- test_simulation.py | 4 ++- 6 files changed, 53 insertions(+), 14 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 dce400ca1fdf9e23e9d33acd4e3a3e193a1432b8..39de5c6dba34d230dbf0f71ae1ee8c261f69f482 100644 GIT binary patch delta 1224 zcmbV~UuauZ9LLYO$-T+Vzb#3-=C-}f?b>GDZq}G4T{Z1en@Hzqi!0mGHZF6)bE7eJ zg=AHlA{$a<1Hshuuv?0>6T~|d)*7MN)8dl>A3P#RXb~Jf`jW27-o%q_Y-J9?{T|NY z@cI3|zc1(aTc6&UwEbYUng9*OdOf}3Ub4BMv~);}FyI70%pyR#ub4UpAkLxzWLSi8 z4hfhV3$X;Smz|qHowyDC56bf%PkG$cL{OT422j|7eIj1bnaJMgb}o#Do(bLecOF zD=X|fF?@k-Wc&VA`2;?@+KfLo&bP?u*aJ(l(!K&vzjAN|e5OM$KJELZN4~rE`X&Ys zuJHm4;dNfXuj|d+Je-Gz!^NQK4+tGtvit(M;(d#{pSi~rm&6qG)BrpQo2$*D`qr{{ zEk^|T>ycZdH%GPOH63gU{RN>%aslzoPzQm4DLJV-;yZmfv;}>6UD)53H%I2?C6Dy#>6u$S)n_rktbqei3fGK|#DWx#QQdCly0R&9=X_-HuFvEK=Z=8Q-V15*g z5>r-EUre#Y7&k^^Y}(Aiq$`#rZq7^5S0Erg}zH{C= z=g9TIbLu;z(SYbVUvgLcv--}db69uhrK%ww@i>IMh@b()lQqPvz9uXTA+MUh#%tHG zSHpMlI*M9ezlOXz{#Cw&e(41RU;5Ssgf_UuV}b-EeSY@L!+TCnCs_E4TO8l=k=lm! z(tWN+e@Un+Ha$`gt6Kzh@TRoQm1$drYUY$iwHDkWUDu@k_31AQHGCW8YCrK(MrmDF zB3;&AD+DUL#W+CpQ`v*{+sPpp`ke2`&^@BMZ>MYx5@K=4ql*<-e+$#|=Ar7@q*=v^;(bX8bTVx620= zJ}UE3n2SMq{QJ|1Lygo?^X^sf_+f4y79#Ro6k?0uPwu92&k$O|J>=*F4iQS>UP7VL zPJTzI%t4+=`!(NcY{=Yj*qOg;{gF7b7VBU{Op3mc)$UsibXos7^;vCqo9*JI_W6Bm zrD6SIga$c%p_EV*Wvx$DN?+Dh8S1m==+*I&!7fRzE7RKw_Zq14*Se~H=DeBk@xr3@ zLT~GhrXqrU+?rK&w_TwMy{U|~(fUR-#25LkRAG%y)z2T_la6Y$(rB$F|EPW)cl2ox zI#gkd50L2qy?B7k^yQi6EUr94^l{u?ikpwCb=dq{`6+BZu5H3)hwgl;@dL67&5rFU z825m~3*JeX@+(uKJSDActsFH5METmX*ryRPzDUyT`u=TYutF@z1*J8xKS>> z-&Em;ZVz~8!509J2*H>VOv%9%h)b}v4Cxix$qepion$~;n8pJP2FMVDVKPQ&$S`rT z5C+sZL|hC^Zq{tQhe#_;HMgMK2`-i%IQ`(CgW`FL#UYVa6034zmEKFx$)V6g91Qx% zFjMFwqfCJTH9Dv)1=BeBgDTu0Po>o+%R=SD$_GYx-3^`@m|XyO0K}*wCS@@RffS^c zVEG-&l*WCGi24+E$OH>vK#hYZRhe9ca@?$(3g6#abVmw*Z^8#Y zx(A{%NB1BC{wPFa5MNf}D{_1VVyvv|#Ln0p^v6*N2N_`!45-n5QkH3~ATds!a(e0U z%foa&?Ayuh`JJ8}VaElIf|~t*jSBR+z&!(wS(x>KKcM)dGELSOW2>9YD#=wj$&yVL cdQgH&dRazlJK2-IXznq@G2$u`XXqCE1ur4rO#lD@ diff --git a/bitcoin.py b/bitcoin.py new file mode 100644 index 0000000..54f7beb --- /dev/null +++ b/bitcoin.py @@ -0,0 +1,22 @@ +import requests + + +def calculate_value(amount, price): + """ + Calculates the value of the Bitcoin amount at the given price. + """ + return float(amount * price) + + +def get_bitcoin_price(): + """ + Fetches the current Bitcoin price from CoinDesk API. + """ + url = "https://api.coindesk.com/v1/bpi/currentprice.json" + response = requests.get(url) + + if response.status_code != 200: + raise ConnectionError("Failed to fetch Bitcoin price") + + data = response.json() + return data["bpi"]["USD"]["rate_float"] diff --git a/bitcoin_trading_simulation.py b/bitcoin_trading_simulation.py index ac406b6..31c5fff 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: @@ -29,6 +28,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. @@ -96,7 +96,8 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): portfolio.loc[i, 'btc'] += btc_to_buy portfolio.loc[i, 'cash'] -= btc_to_buy * row['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} BTC at " + f"${row['price']:.2f}{Colors.ENDC}") # Sell signal elif row['positions'] == -2.0: @@ -104,7 +105,8 @@ def simulate_trading(signals, initial_cash=10000, quiet=False): cash_received = portfolio.loc[i, 'btc'] * row['price'] portfolio.loc[i, 'cash'] += cash_received if not quiet: - print(f"{Colors.FAIL}🔴 Day {i}: Sell {portfolio.loc[i, 'btc']:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}") + print(f"{Colors.FAIL}🔴 Day {i}: Sell {portfolio.loc[i, 'btc']:.4f} BTC at " + f"${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'] @@ -156,21 +158,29 @@ 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] - + print(f"\n{Colors.HEADER}{Colors.BOLD}╔══════════════════════════════════════════╗{Colors.ENDC}") print(f"{Colors.HEADER}{Colors.BOLD}║ Final Portfolio Performance ║{Colors.ENDC}") print(f"{Colors.HEADER}{Colors.BOLD}╠══════════════════════════════════════════╣{Colors.ENDC}") - print(f"{Colors.HEADER}║{Colors.ENDC} {'Initial Cash:':<25} ${initial_cash:>14,.2f} {Colors.HEADER}║{Colors.ENDC}") - print(f"{Colors.HEADER}║{Colors.ENDC} {'Final Value:':<25} ${final_value:>14,.2f} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {'Initial Cash:':<25} ${initial_cash:>14,.2f} " + f"{Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {'Final Value:':<25} ${final_value:>14,.2f} " + f"{Colors.HEADER}║{Colors.ENDC}") if profit >= 0: - print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.GREEN}💰 Profit/Loss: ${profit:>14,.2f}{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") - print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.GREEN} ROI: {roi:>14.2f}%{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.GREEN}💰 Profit/Loss: " + f"${profit:>14,.2f}{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.GREEN} ROI: " + f"{roi:>14.2f}%{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") else: - print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.FAIL}📉 Profit/Loss: ${profit:>14,.2f}{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") - print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.FAIL} ROI: {roi:>14.2f}%{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.FAIL}📉 Profit/Loss: " + f"${profit:>14,.2f}{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {Colors.FAIL} ROI: " + f"{roi:>14.2f}%{Colors.ENDC} {Colors.HEADER}║{Colors.ENDC}") print(f"{Colors.HEADER}╠══════════════════════════════════════════╣{Colors.ENDC}") - print(f"{Colors.HEADER}║{Colors.ENDC} {'Trades (B/S):':<25} {f'{buys}/{sells}':>15} {Colors.HEADER}║{Colors.ENDC}") - print(f"{Colors.HEADER}║{Colors.ENDC} {'Buy & Hold:':<25} ${buy_and_hold_value:>14,.2f} {Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {'Trades (B/S):':<25} {f'{buys}/{sells}':>15} " + f"{Colors.HEADER}║{Colors.ENDC}") + print(f"{Colors.HEADER}║{Colors.ENDC} {'Buy & Hold:':<25} ${buy_and_hold_value:>14,.2f} " + f"{Colors.HEADER}║{Colors.ENDC}") print(f"{Colors.HEADER}{Colors.BOLD}╚══════════════════════════════════════════╝{Colors.ENDC}") 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 = {