Skip to content

Commit 193c871

Browse files
commit
1 parent 281d6c3 commit 193c871

File tree

12 files changed

+389
-102
lines changed

12 files changed

+389
-102
lines changed

README.md

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
> **Unleash the power of modular, realistic, and extensible portfolio simulation.**
32
43
---
@@ -108,6 +107,38 @@ flowchart TD
108107

109108
---
110109

110+
## 🏗️ Project Structure & Architecture
111+
112+
- `contracts/` — Core contracts and abstract base classes (Portfolio, StrategyBase, Executor)
113+
- `core/` — Core logic, execution engines, simulation loop
114+
- `strategies/` — Example and user strategies (momentum, buy & hold, etc.)
115+
- `data_ingestion/`, `data_cache/` — Data loaders, adapters, and caching for reproducible research
116+
- `analytics/`, `ml_engine/` — Analytics, reporting, and ML integrations
117+
- `dashboard/` — Streamlit/Dash dashboard for visualization
118+
- `run_backtest.py` — CLI entry point to run backtests
119+
- `main.py`, `run/` — Additional CLI tools and runners
120+
121+
---
122+
123+
## 🚦 Development Roadmap (Next Steps)
124+
125+
1. **Finalize Core Contracts**
126+
- Audit and refine `Portfolio`, `StrategyBase`, and `PortfolioExecutor` for strict modularity and safety (no future leaks).
127+
2. **Strategy API**
128+
- Enforce and document the `generate_signals` interface. Add more example strategies.
129+
3. **Backtesting Engine**
130+
- Expand test coverage and logging in `run_backtest.py` and `core/executors/backtest.py`.
131+
4. **Data Layer**
132+
- Ensure robust, reproducible data ingestion and caching. Document data contracts.
133+
5. **CLI & Developer Experience**
134+
- Improve CLI usability and add clear usage examples.
135+
6. **Dashboard & Analytics**
136+
- Expand analytics and dashboard integration for portfolio and strategy reporting.
137+
7. **Documentation**
138+
- Add docstrings, inline docs, and contribution guidelines for new modules and strategies.
139+
140+
---
141+
111142
## 🔧 Core Components
112143

113144
| Module | Purpose |
@@ -132,44 +163,28 @@ flowchart TD
132163

133164
---
134165

135-
## 🚀 Quick Start
136-
137-
```sh
138-
# Install dependencies
139-
pip install -r requirements.txt
166+
## 🚀 Quickstart
140167

141-
# Run a backtest
142-
python run_backtest.py --strategy momentum --tickers AAPL,MSFT --start 2023-01-01 --end 2023-12-31 --plot
143-
144-
# Or use the Streamlit UI
145-
streamlit run streamlit_app.py
146-
```
168+
1. **Install Requirements**
169+
```bash
170+
pip install -r requirements.txt
171+
```
172+
2. **Run a Backtest**
173+
```bash
174+
python run_backtest.py --strategy strategies/stock/momentum.py --portfolio configs/sample_portfolio.yaml
175+
```
176+
3. **Add a New Strategy**
177+
- Implement a new class in `strategies/` inheriting from `StrategyBase` and implementing `generate_signals()`.
178+
- Register your strategy in your backtest config or CLI.
147179

148180
---
149181

150-
## 🧩 Project Structure
151-
152-
```text
153-
📦traderplusplus
154-
├── contracts
155-
│ ├── asset.py # Asset & CashAsset classes
156-
│ └── portfolio.py # Portfolio definition
157-
├── core
158-
│ ├── backtester.py # Runs simulation
159-
│ ├── executor.py # Executes trades
160-
│ ├── market_data.py # Loads, stores & queries market data
161-
│ ├── data_loader.py # Yahoo/Polygon loaders + caching
162-
│ ├── guardrails # Risk guardrail classes
163-
│ └── visualizer.py # Matplotlib + Plotly charts
164-
├── strategies
165-
│ ├── base.py # StrategyBase + factory
166-
│ └── stock
167-
│ ├── momentum.py # Example strategy
168-
├── analytics
169-
│ └── performance.py # Sharpe, Alpha etc.
170-
├── run_backtest.py # CLI tool
171-
└── streamlit_app.py # UI
172-
```
182+
## 🤝 Contributing
183+
184+
- See the Development Roadmap above for high-priority areas.
185+
- Add new strategies, data adapters, or analytics modules as composable units.
186+
- Follow modular design and document your code.
187+
- PRs and issues welcome!
173188

174189
---
175190

@@ -179,12 +194,6 @@ See the MVP Roadmap above for our ambitious next steps!
179194

180195
---
181196

182-
## 🙌 Contributing
183-
184-
Pull requests and suggestions are welcome! For major changes, please open an issue first to discuss what you’d like to change.
185-
186-
---
187-
188197
## 📄 License
189198

190199
Distributed under the MIT License.

contracts/asset.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ def __init__(self, ticker: str, shares: int = 0):
77
self.shares = shares
88
self.trade_history = []
99

10+
@property
11+
def balance(self):
12+
return self.shares
13+
1014
def buy(self, quantity: int):
1115
self.shares += quantity
1216

core/backtester.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,27 @@ def run(self, tickers: List[str], start_date: str, end_date: str):
3737
common_index = common_index.sort_values()
3838
for current_date in common_index:
3939
# --- PURE STRATEGY: ONLY GENERATE SIGNALS ---
40-
signals = self.strategy.generate_signals(self.market_data, current_date=current_date)
40+
signals = self.strategy.generate_signals(self.market_data, current_date=current_date,
41+
positions=self.portfolio.positions)
4142
# --- EXECUTOR: SUBMIT ORDERS BASED ON SIGNALS ---
42-
for symbol, signal in signals.items():
43-
if signal == 1:
44-
order = Order(symbol=symbol, side=OrderSide.BUY, quantity=1, order_type=OrderType.MARKET)
45-
self.executor.submit_order(order)
46-
elif signal == -1:
47-
order = Order(symbol=symbol, side=OrderSide.SELL, quantity=1, order_type=OrderType.MARKET)
43+
for symbol, order_size in signals.items():
44+
if order_size == 0:
45+
continue
46+
order_side = OrderSide.BUY if order_size > 0 else OrderSide.SELL
47+
if order_side is not None:
48+
order = Order(
49+
symbol=symbol,
50+
side=order_side,
51+
quantity=order_size,
52+
order_type=OrderType.MARKET
53+
)
4854
self.executor.submit_order(order)
55+
# if signal == 1:
56+
# order = Order(symbol=symbol, side=OrderSide.BUY, quantity=1, order_type=OrderType.MARKET)
57+
# self.executor.submit_order(order)
58+
# elif signal == -1:
59+
# order = Order(symbol=symbol, side=OrderSide.SELL, quantity=1, order_type=OrderType.MARKET)
60+
# self.executor.submit_order(order)
4961
# --- EXECUTOR: ADVANCE TO NEXT STEP ---
5062
self.executor.step(current_date)
5163

core/executors/backtest.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ def step(self, current_time):
5959
message='Guardrail blocked order')
6060
continue
6161
try:
62-
if order.side.name == 'BUY':
63-
self.portfolio.execute_trade(current_time, order.symbol, 'BUY', fill_qty, avg_fill_price,
64-
note='Backtest Fill')
65-
else:
66-
self.portfolio.execute_trade(current_time, order.symbol, 'SELL', fill_qty, avg_fill_price,
67-
note='Backtest Fill')
62+
self.portfolio.execute_trade(current_time, order.symbol, order.side.name, fill_qty, avg_fill_price,
63+
note='Backtest Fill')
64+
# if order.side.name == 'BUY':
65+
# self.portfolio.execute_trade(current_time, order.symbol, 'BUY', fill_qty, avg_fill_price,
66+
# note='Backtest Fill')
67+
# else:
68+
# self.portfolio.execute_trade(current_time, order.symbol, 'SELL', fill_qty, avg_fill_price,
69+
# note='Backtest Fill')
6870
self.order_status[order_id] = OrderStatus.FILLED
6971
self.fills[order_id] = OrderResult(order_id=order_id, status=OrderStatus.FILLED,
7072
filled_quantity=fill_qty, avg_fill_price=avg_fill_price)
@@ -80,6 +82,8 @@ def step(self, current_time):
8082
if price is not None:
8183
net_worth += shares * price
8284

85+
if net_worth != 50000:
86+
print('wth')
8387
self.equity_curve.append({'date': current_time, 'net_worth': net_worth})
8488

8589
def get_equity_curve(self):

core/market_data.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ def from_ingestion(cls,
3737

3838
def get_price(self, ticker: str, date: pd.Timestamp, price_type='Close') -> float | None:
3939
try:
40+
if ticker == 'CASH':
41+
return 1.0
4042
return self.data[ticker].loc[date, price_type]
4143
except KeyError:
4244
return None

0 commit comments

Comments
 (0)