Skip to content

Commit 35be8d8

Browse files
small tweaks
1 parent cddb598 commit 35be8d8

File tree

6 files changed

+71
-6
lines changed

6 files changed

+71
-6
lines changed

core/visualizer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ def plotly_interactive_equity(equity_curve: pd.Series,
201201
fig.write_html(html_path)
202202
print(f"Interactive plot saved to {html_path}")
203203

204-
return fig
204+
# Explicitly set figure to None to help with garbage collection
205+
# This should help prevent semaphore leaks
206+
fig = None
205207

206208

207209
def plotly_equity_vs_benchmark(
@@ -263,4 +265,6 @@ def plotly_equity_vs_benchmark(
263265
fig.write_html(html_path)
264266
print(f"Interactive plot saved to {html_path}")
265267

266-
return fig
268+
# Explicitly set figure to None to help with garbage collection
269+
# This should help prevent semaphore leaks
270+
fig = None

data_ingestion/yahoo_fetcher.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import pandas as pd
12
import yfinance as yf
23

34

4-
def fetch_yahoo_data(ticker: str, start_date: str, end_date: str):
5-
data = yf.download(ticker, start=start_date, end=end_date, multi_level_index=False, threads=False)
5+
def fetch_yahoo_data(ticker: str, start_date: str, end_date: str, interval: str = "1d") -> pd.DataFrame:
6+
data = yf.download(ticker, start=start_date, end=end_date, interval=interval, multi_level_index=False, threads=False)
67
return data

guardrails/trailing_stop_loss.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
@GuardrailFactory.register("trailing_stop_loss")
88
class TrailingStopLossGuardrail(GuardrailBase):
9-
def __init__(self, stop_pct: float = 0.05):
9+
def __init__(self, stop_pct: float = 0.07):
10+
super().__init__()
1011
self.stop_pct = stop_pct
1112
self.entry_prices = {}
1213
self.highest_since_entry = {}

run_backtest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def main():
6363
bt.run(start_date=args.start, end_date=args.end)
6464

6565
# --- Backtest Results ---
66-
print(f"\n🚀 Backtest completed for {portfolio.name} with {len(tickers)} tickers from {args.start} to {args.end}")
66+
print(f"\n🚀 Backtest completed for {portfolio.name} with {len(tickers)} tickers ({', '.join(tickers)}) from {args.start} to {args.end}")
6767
print(f"💰 Starting Cash: ${args.cash:,.2f}")
6868
print(f"📈 Final Net Worth: ${bt.get_final_net_worth():,.2f}")
6969

strategies/stock/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from .buy_n_hold import *
44
from .capm_portfolio import *
55
from .no_invest import *
6+
from .stoploss import *

strategies/stock/stoploss.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from abc import ABC
2+
from typing import Dict, Optional
3+
4+
import pandas as pd
5+
6+
from contracts.asset import Asset, CashAsset
7+
from core.market_data import MarketData
8+
from strategies.stock.base import StrategyBase, StrategyFactory
9+
10+
11+
@StrategyFactory.register("stoploss")
12+
class StoplossStrategy(StrategyBase, ABC):
13+
def __init__(self, **kwargs):
14+
self.lookback_period = 7
15+
self.has_bought: Optional[float] = None
16+
self.trail_pct: float = kwargs.get("trail_pct", 0.04)
17+
18+
def get_name(self) -> str:
19+
return "stoploss"
20+
21+
def get_config(self) -> Dict:
22+
return {
23+
"type": self.get_name()
24+
}
25+
26+
def generate_signals(self, price_data: pd.DataFrame | Dict[str, pd.DataFrame], current_date: pd.Timestamp,
27+
positions: Dict[str, Asset], cash: float, **kwargs) -> Optional[Dict[str, int]]:
28+
"""
29+
Buy once and hold till perpetuity for the strategy.
30+
:param price_data: A DataFrame or dict of DataFrames containing price data for each asset.
31+
:param cash:
32+
:param current_date:
33+
:param positions:
34+
:return:
35+
"""
36+
ticker = list(positions.keys())[0] # Single ticker strategy
37+
signals = {}
38+
39+
if self.has_bought:
40+
# If already bought, SELL if cur_price lower than trailing stop-loss
41+
cur_price = price_data[ticker]['Close'].loc[current_date]
42+
if cur_price < (self.has_bought * (1-self.trail_pct)):
43+
# SELL
44+
signals[ticker] = -positions[ticker].shares
45+
self.has_bought = None
46+
elif cur_price > self.has_bought:
47+
self.has_bought = cur_price
48+
49+
else:
50+
pct_change = price_data[ticker]['Close'].pct_change().dropna()
51+
avg_pct_change = pct_change.mean()
52+
cur_pct_change = pct_change[-int(self.lookback_period/2):].mean()
53+
if cur_pct_change > 0 and cur_pct_change > avg_pct_change:
54+
cur_price = price_data[ticker]['Close'].loc[current_date]
55+
signals[ticker] = int(cash / cur_price)
56+
self.has_bought = cur_price
57+
58+
return signals

0 commit comments

Comments
 (0)