Skip to content

Commit db965a8

Browse files
authored
Merge pull request #52 from EiJackGH/main
Develop
2 parents 3930934 + 538a32c commit db965a8

11 files changed

Lines changed: 298 additions & 54 deletions

.Jules/palette.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
## 2024-05-22 - Visual Hierarchy in CLI Output
2-
**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.
3-
**Action:** For data-heavy CLI applications, always implement a semantic color system and visual anchors (icons/emojis) for key events.
4-
5-
## 2024-05-23 - Accessibility and Control in CLI Tools
6-
**Learning:** While color and emojis improve scannability, they can be distracting or inaccessible (e.g., for color-blind users or automated parsing). Providing `--no-color` and `--quiet` flags is essential for accessibility and flexibility.
7-
**Action:** Always include flags to disable visual enhancements and suppress verbose output in CLI tools to respect user preferences and support automation.
1+
## 2024-05-23 - CLI UX Enhancement
2+
**Learning:** Even in CLI apps, visual distinction (colors, emojis) significantly reduces cognitive load when scanning logs.
3+
**Action:** Use ANSI colors and consistent emojis for key events (success/failure) in future CLI tools.

.github/workflows/codeql.yml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# For most projects, this workflow file will not need changing; you simply need
2+
# to commit it to your repository.
3+
#
4+
# You may wish to alter this file to override the set of languages analyzed,
5+
# or to provide custom queries or build logic.
6+
#
7+
# ******** NOTE ********
8+
# We have attempted to detect the languages in your repository. Please check
9+
# the `language` matrix defined below to confirm you have the correct set of
10+
# supported CodeQL languages.
11+
#
12+
name: "CodeQL Advanced"
13+
14+
on:
15+
push:
16+
branches: [ "main" ]
17+
pull_request:
18+
branches: [ "main" ]
19+
schedule:
20+
- cron: '42 16 * * 6'
21+
22+
jobs:
23+
analyze:
24+
name: Analyze (${{ matrix.language }})
25+
# Runner size impacts CodeQL analysis time. To learn more, please see:
26+
# - https://gh.io/recommended-hardware-resources-for-running-codeql
27+
# - https://gh.io/supported-runners-and-hardware-resources
28+
# - https://gh.io/using-larger-runners (GitHub.com only)
29+
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
30+
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
31+
permissions:
32+
# required for all workflows
33+
security-events: write
34+
35+
# required to fetch internal or private CodeQL packs
36+
packages: read
37+
38+
# only required for workflows in private repositories
39+
actions: read
40+
contents: read
41+
42+
strategy:
43+
fail-fast: false
44+
matrix:
45+
include:
46+
- language: actions
47+
build-mode: none
48+
- language: python
49+
build-mode: none
50+
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
51+
# Use `c-cpp` to analyze code written in C, C++ or both
52+
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
53+
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
54+
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
55+
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
56+
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
57+
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
58+
steps:
59+
- name: Checkout repository
60+
uses: actions/checkout@v4
61+
62+
# Add any setup steps before running the `github/codeql-action/init` action.
63+
# This includes steps like installing compilers or runtimes (`actions/setup-node`
64+
# or others). This is typically only required for manual builds.
65+
# - name: Setup runtime (example)
66+
# uses: actions/setup-example@v1
67+
68+
# Initializes the CodeQL tools for scanning.
69+
- name: Initialize CodeQL
70+
uses: github/codeql-action/init@v4
71+
with:
72+
languages: ${{ matrix.language }}
73+
build-mode: ${{ matrix.build-mode }}
74+
# If you wish to specify custom queries, you can do so here or in a config file.
75+
# By default, queries listed here will override any specified in a config file.
76+
# Prefix the list here with "+" to use these queries and those in the config file.
77+
78+
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
79+
# queries: security-extended,security-and-quality
80+
81+
# If the analyze step fails for one of the languages you are analyzing with
82+
# "We were unable to automatically build your code", modify the matrix above
83+
# to set the build mode to "manual" for that language. Then modify this step
84+
# to build your code.
85+
# ℹ️ Command-line programs to run using the OS shell.
86+
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
87+
- name: Run manual build steps
88+
if: matrix.build-mode == 'manual'
89+
shell: bash
90+
run: |
91+
echo 'If you are using a "manual" build mode for one or more of the' \
92+
'languages you are analyzing, replace this with the commands to build' \
93+
'your code, for example:'
94+
echo ' make bootstrap'
95+
echo ' make release'
96+
exit 1
97+
98+
- name: Perform CodeQL Analysis
99+
uses: github/codeql-action/analyze@v4
100+
with:
101+
category: "/language:${{matrix.language}}"

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,3 @@
4343
# Python
4444
__pycache__/
4545
*.pyc
46-
.pytest_cache/

README.md

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ A Python-based CLI tool that simulates Bitcoin trading using a 'Golden Cross' mo
44

55
## Features
66

7-
- **Price Simulation:** Uses Geometric Brownian Motion to simulate 60 days of Bitcoin prices.
7+
- **Price Simulation:** Uses Geometric Brownian Motion to simulate Bitcoin prices.
88
- **Trading Strategy:** Implements a Golden Cross strategy (Short MA > Long MA = Buy, Short MA < Long MA = Sell).
9-
- **Rich CLI Output:** features color-coded logs (Green for Buy/Profit, Red for Sell/Loss) and emojis for better readability.
9+
- **Rich CLI Output:** Features color-coded logs (Green for Buy/Profit, Red for Sell/Loss) and emojis for better readability.
1010
- **Performance metrics:** Compares the strategy's performance against a "Buy and Hold" approach.
11+
- **Customizable:** Configure simulation parameters via command-line arguments.
1112

1213
## Installation
1314

@@ -20,16 +21,36 @@ pip install -r requirements.txt
2021

2122
## Usage
2223

23-
Run the simulation script:
24+
Run the simulation script with default settings (60 days, $10k initial cash):
2425

2526
```bash
2627
python bitcoin_trading_simulation.py
2728
```
2829

30+
### Options
31+
32+
Customize the simulation with the following arguments:
33+
34+
```bash
35+
python bitcoin_trading_simulation.py --days 100 --initial-cash 5000 --initial-price 60000 --volatility 0.03
36+
```
37+
38+
- `--days`: Number of days to simulate (default: 60)
39+
- `--initial-cash`: Initial cash amount (default: 10000)
40+
- `--initial-price`: Initial Bitcoin price (default: 50000)
41+
- `--volatility`: Price volatility (default: 0.02)
42+
- `--quiet`: Suppress daily portfolio log (only show final result)
43+
- `--no-color`: Disable colored output (for accessibility or logging)
44+
45+
Example:
46+
```bash
47+
python bitcoin_trading_simulation.py --days 30 --quiet --no-color
48+
```
49+
2950
## Tests
3051

31-
Run the test suite:
52+
Run the test suite using `pytest`:
3253

3354
```bash
34-
python test.py
55+
pytest
3556
```
8.21 KB
Binary file not shown.
5.05 KB
Binary file not shown.

bitcoin_trading_simulation.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import argparse
12
import numpy as np
23
import pandas as pd
34
import argparse
45

6+
57
class Colors:
68
HEADER = '\033[95m'
79
BLUE = '\033[94m'
@@ -19,6 +21,18 @@ def disable(cls):
1921
cls.ENDC = ''
2022
cls.BOLD = ''
2123

24+
25+
class Colors:
26+
HEADER = '\033[95m'
27+
BLUE = '\033[94m'
28+
CYAN = '\033[96m'
29+
GREEN = '\033[92m'
30+
WARNING = '\033[93m'
31+
FAIL = '\033[91m'
32+
ENDC = '\033[0m'
33+
BOLD = '\033[1m'
34+
UNDERLINE = '\033[4m'
35+
2236
def simulate_bitcoin_prices(days=60, initial_price=50000, volatility=0.02):
2337
"""
2438
Simulates Bitcoin prices for a given number of days using Geometric Brownian Motion.
@@ -33,6 +47,7 @@ def simulate_bitcoin_prices(days=60, initial_price=50000, volatility=0.02):
3347
prices.append(prices[-1] + price_change)
3448
return pd.Series(prices, name='Price')
3549

50+
3651
def calculate_moving_averages(prices, short_window=7, long_window=30):
3752
"""
3853
Calculates short and long moving averages for a given price series.
@@ -43,6 +58,7 @@ def calculate_moving_averages(prices, short_window=7, long_window=30):
4358
signals['long_mavg'] = prices.rolling(window=long_window, min_periods=1, center=False).mean()
4459
return signals
4560

61+
4662
def generate_trading_signals(signals):
4763
"""
4864
Generates trading signals based on the Golden Cross strategy.
@@ -54,11 +70,12 @@ def generate_trading_signals(signals):
5470
signals.loc[signals['short_mavg'] > signals['long_mavg'], 'signal'] = 1.0
5571
# A Death Cross (sell signal)
5672
signals.loc[signals['short_mavg'] < signals['long_mavg'], 'signal'] = -1.0
57-
73+
5874
# We create 'positions' to represent the trading action: 1 for buy, -1 for sell, 0 for hold
5975
signals['positions'] = signals['signal'].diff().shift(1)
6076
return signals
6177

78+
6279
def simulate_trading(signals, initial_cash=10000, quiet=False):
6380
"""
6481
Simulates trading based on signals and prints a daily ledger.
@@ -71,6 +88,8 @@ def simulate_trading(signals, initial_cash=10000, quiet=False):
7188

7289
if not quiet:
7390
print(f"{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}")
91+
92+
print(f"\n{Colors.HEADER}{Colors.BOLD}------ Daily Trading Ledger ------{Colors.ENDC}")
7493
for i, row in signals.iterrows():
7594
if i > 0:
7695
portfolio.loc[i, 'cash'] = portfolio.loc[i-1, 'cash']
@@ -81,30 +100,33 @@ def simulate_trading(signals, initial_cash=10000, quiet=False):
81100
btc_to_buy = portfolio.loc[i, 'cash'] / row['price']
82101
portfolio.loc[i, 'btc'] += btc_to_buy
83102
portfolio.loc[i, 'cash'] -= btc_to_buy * row['price']
84-
print(f"{Colors.GREEN}Day {i}: 💰 Buy {btc_to_buy:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}")
103+
print(f"{Colors.GREEN}🟢 Day {i}: Buy {btc_to_buy:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}")
85104

86105
# Sell signal
87106
elif row['positions'] == -2.0:
88107
if portfolio.loc[i, 'btc'] > 0:
89108
cash_received = portfolio.loc[i, 'btc'] * row['price']
90109
portfolio.loc[i, 'cash'] += cash_received
91-
print(f"{Colors.RED}Day {i}: 📉 Sell {portfolio.loc[i, 'btc']:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}")
110+
print(f"{Colors.FAIL}🔴 Day {i}: Sell {portfolio.loc[i, 'btc']:.4f} BTC at ${row['price']:.2f}{Colors.ENDC}")
92111
portfolio.loc[i, 'btc'] = 0
93112

94113
portfolio.loc[i, 'total_value'] = portfolio.loc[i, 'cash'] + portfolio.loc[i, 'btc'] * row['price']
114+
95115
if not quiet:
96-
print(f"Day {i}: Portfolio Value: ${portfolio.loc[i, 'total_value']:.2f}, Cash: ${portfolio.loc[i, 'cash']:.2f}, BTC: {portfolio.loc[i, 'btc']:.4f}")
97-
116+
print(f"Day {i}: Portfolio Value: ${portfolio.loc[i, 'total_value']:.2f}, "
117+
f"Cash: ${portfolio.loc[i, 'cash']:.2f}, BTC: {portfolio.loc[i, 'btc']:.4f}")
118+
98119
return portfolio
99120

121+
100122
if __name__ == "__main__":
101-
parser = argparse.ArgumentParser(description='Bitcoin Trading Simulation')
102-
parser.add_argument('--days', type=int, default=60, help='Number of days to simulate')
103-
parser.add_argument('--initial-cash', type=float, default=10000, help='Initial cash in portfolio')
104-
parser.add_argument('--initial-price', type=float, default=50000, help='Initial Bitcoin price')
105-
parser.add_argument('--volatility', type=float, default=0.02, help='Volatility of price changes')
106-
parser.add_argument('--quiet', '-q', action='store_true', help='Suppress daily portfolio logs')
107-
parser.add_argument('--no-color', action='store_true', help='Disable colored output')
123+
parser = argparse.ArgumentParser(description="Bitcoin Trading Simulation")
124+
parser.add_argument("--days", type=int, default=60, help="Number of days to simulate")
125+
parser.add_argument("--initial-cash", type=float, default=10000, help="Initial cash amount")
126+
parser.add_argument("--initial-price", type=float, default=50000, help="Initial Bitcoin price")
127+
parser.add_argument("--volatility", type=float, default=0.02, help="Price volatility")
128+
parser.add_argument("--quiet", action="store_true", help="Suppress daily portfolio log")
129+
parser.add_argument("--no-color", action="store_true", help="Disable colored output")
108130

109131
args = parser.parse_args()
110132

@@ -113,33 +135,31 @@ def simulate_trading(signals, initial_cash=10000, quiet=False):
113135

114136
# Simulate prices
115137
prices = simulate_bitcoin_prices(days=args.days, initial_price=args.initial_price, volatility=args.volatility)
116-
138+
117139
# Calculate moving averages
118140
signals = calculate_moving_averages(prices)
119-
141+
120142
# Generate trading signals
121143
signals = generate_trading_signals(signals)
122-
144+
123145
# Simulate trading
124146
portfolio = simulate_trading(signals, initial_cash=args.initial_cash, quiet=args.quiet)
125-
147+
126148
# Final portfolio performance
127149
final_value = portfolio['total_value'].iloc[-1]
128150
initial_cash = args.initial_cash
129151
profit = final_value - initial_cash
130-
152+
131153
# Compare with buy and hold strategy
132-
buy_and_hold_btc = initial_cash / prices.iloc[0]
154+
buy_and_hold_btc = args.initial_cash / prices.iloc[0]
133155
buy_and_hold_value = buy_and_hold_btc * prices.iloc[-1]
134156

135157
print(f"\n{Colors.HEADER}{Colors.BOLD}------ Final Portfolio Performance ------{Colors.ENDC}")
136158
print(f"Initial Cash: ${initial_cash:.2f}")
137159
print(f"Final Portfolio Value: ${final_value:.2f}")
138-
139160
if profit >= 0:
140-
print(f"Profit/Loss: {Colors.GREEN}📈 ${profit:.2f}{Colors.ENDC}")
161+
print(f"{Colors.GREEN}💰 Profit/Loss: ${profit:.2f}{Colors.ENDC}")
141162
else:
142-
print(f"Profit/Loss: {Colors.RED}📉 ${profit:.2f}{Colors.ENDC}")
143-
163+
print(f"{Colors.FAIL}📉 Profit/Loss: ${profit:.2f}{Colors.ENDC}")
144164
print(f"Buy and Hold Strategy Value: ${buy_and_hold_value:.2f}")
145165
print(f"{Colors.HEADER}-----------------------------------------{Colors.ENDC}")

test.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

test_bitcoin.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import pytest
2+
from unittest.mock import patch
3+
from bitcoin import get_bitcoin_price, calculate_value
4+
5+
# Test 1: Verify the calculation logic
6+
def test_calculate_value():
7+
"""Ensure BTC to USD conversion math is correct."""
8+
price = 50000.0
9+
amount = 2.5
10+
expected = 125000.0
11+
assert calculate_value(amount, price) == expected
12+
13+
# Test 2: Verify handling of zero amount
14+
def test_calculate_value_zero():
15+
assert calculate_value(0, 50000.0) == 0.0
16+
17+
# Test 3: Mocking an API response
18+
@patch('bitcoin.requests.get')
19+
def test_get_bitcoin_price(mock_get):
20+
"""Simulate a successful API response from CoinDesk or similar."""
21+
# Mock the JSON return value
22+
mock_get.return_value.json.return_value = {
23+
"bpi": {"USD": {"rate_float": 62000.50}}
24+
}
25+
mock_get.return_value.status_code = 200
26+
27+
price = get_bitcoin_price()
28+
assert price == 62000.50
29+
30+
# Test 4: Handling API failure
31+
@patch('bitcoin.requests.get')
32+
def test_get_price_api_error(mock_get):
33+
mock_get.return_value.status_code = 404
34+
with pytest.raises(ConnectionError):
35+
get_bitcoin_price()

0 commit comments

Comments
 (0)