Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
__pycache__/
*/__pycache__/*
.venv/
venv/
.git/
.github/
.streamlit/cache/
data/*
1 change: 0 additions & 1 deletion .streamlit/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ primaryColor = "#F97316"
backgroundColor = "#0F1115"
secondaryBackgroundColor = "#1A1D23"
textColor = "#E5E7EB"
font = "sans serif"
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.11-slim

WORKDIR /app
ENV PIP_NO_CACHE_DIR=1

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,12 @@ source .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt
python -m streamlit run streamlit_app.py

### Ejecutar con Docker
```bash
# Construir la imagen
docker build -t quantboard .

# Ejecutar el contenedor (Streamlit en http://localhost:8501)
docker run -p 8501:8501 quantboard
```
35 changes: 22 additions & 13 deletions pages/02_SMA_Heatmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,27 @@ def main() -> None:
ticker = st.text_input("Ticker", value=ticker_default).strip().upper()
end = st.date_input("To", value=end_default)
start = st.date_input("From", value=start_default)
fast_min, fast_max = st.slider("Fast SMA range", 5, 60, (fast_min_default, fast_max_default))
slow_min, slow_max = st.slider("Slow SMA range", 20, 240, (slow_min_default, slow_max_default))
fast_min, fast_max = st.slider(
"Fast SMA range", 5, 60, (fast_min_default, fast_max_default)
)
slow_min, slow_max = st.slider(
"Slow SMA range", 20, 240, (slow_min_default, slow_max_default)
)
submitted = st.form_submit_button("Run search", type="primary")

if not submitted:
st.info("Choose parameters and click **Run search**.")
return
# If the form was not submitted, show a hint and exit early
if not submitted:
st.info("Choose parameters and click **Run search**.")
return

set_param("ticker", ticker or None)
set_param("heat_end", end)
set_param("heat_start", start)
set_param("heat_fast_min", int(fast_min))
set_param("heat_fast_max", int(fast_max))
set_param("heat_slow_min", int(slow_min))
set_param("heat_slow_max", int(slow_max))
# Persist parameters for sharable links / reload
set_param("ticker", ticker or None)
set_param("heat_end", end)
set_param("heat_start", start)
set_param("heat_fast_min", int(fast_min))
set_param("heat_fast_max", int(fast_max))
set_param("heat_slow_min", int(slow_min))
set_param("heat_slow_max", int(slow_max))

if fast_min >= slow_min:
st.error("Fast SMA range must stay below the Slow SMA range.")
Expand Down Expand Up @@ -107,7 +113,10 @@ def main() -> None:
z.loc[fast_window, slow_window] = float("nan")

st.subheader("Heatmap (Sharpe)")
st.plotly_chart(heatmap_metric(z, title="SMA grid — Sharpe"), use_container_width=True)
st.plotly_chart(
heatmap_metric(z, title="SMA grid — Sharpe"),
use_container_width=True,
)

stacked = z.stack().dropna().astype(float)
if stacked.empty:
Expand Down
44 changes: 38 additions & 6 deletions pages/03_Backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,31 @@ def _run_signals(close: pd.Series, fast: int, slow: int) -> pd.Series:
return sig.replace(0, pd.NA).ffill().fillna(0.0)


def _clean_prices(df: pd.DataFrame) -> pd.DataFrame | None:
price_cols = ["open", "high", "low", "close", "volume"]
numeric = {col: pd.to_numeric(df.get(col), errors="coerce") for col in price_cols if col in df}
clean = df.assign(**numeric).dropna(subset=["close"])
if clean.empty:
st.error("No price data available after cleaning.")
return None
return clean


def _run_signals(close: pd.Series, fast: int, slow: int) -> pd.Series:
if signals_sma_crossover is not None:
sig, _ = signals_sma_crossover(close, fast=fast, slow=slow)
return sig

sma_fast = sma(close, fast)
sma_slow = sma(close, slow)
cross_up = (sma_fast > sma_slow) & (sma_fast.shift(1) <= sma_slow.shift(1))
cross_dn = (sma_fast < sma_slow) & (sma_fast.shift(1) >= sma_slow.shift(1))
sig = pd.Series(0.0, index=close.index)
sig = sig.where(~cross_up, 1.0)
sig = sig.where(~cross_dn, -1.0)
return sig.replace(0, pd.NA).ffill().fillna(0.0)


def main() -> None:
st.title("Backtest — SMA crossover")

Expand Down Expand Up @@ -142,12 +167,7 @@ def main() -> None:
fee_bps = st.number_input("Fees (bps)", 0, 50, int(fee_default), step=1)
slip_bps = st.number_input("Slippage (bps)", 0, 50, int(slip_default), step=1)
submitted = st.form_submit_button("Run backtest", type="primary")

st.info("Configure the sidebar parameters and run the backtest.")

if not submitted:
return


set_param("ticker", ticker or None)
set_param("bt_end", end)
set_param("bt_start", start)
Expand All @@ -157,6 +177,18 @@ def main() -> None:
set_param("fee_bps", int(fee_bps))
set_param("slippage_bps", int(slip_bps))

st.info("Configure the sidebar parameters and run the backtest.")

if not submitted:
return

if fast >= slow:
st.error("Fast SMA must be smaller than Slow SMA.")
return

with st.spinner("Fetching data..."):
df = _load_prices(ticker, start=start, end=end)

if fast >= slow:
st.error("Fast SMA must be smaller than Slow SMA.")
return
Expand Down
6 changes: 4 additions & 2 deletions pages/2_Optimizacion_SMA.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@

shareable_link_button()

st.info("Esta página fue reemplazada por **SMA Heatmap**. Usá el ítem *SMA Heatmap* del menú para optimizar parámetros.")

st.info(
"This page was replaced by **SMA Heatmap**. "
"Use the *SMA Heatmap* menu item to optimize parameters."
)