"""BBCA forecast desk. Every button press pulls fresh prices and refits.
python app.py # real data from Yahoo Finance
python app.py --demo # synthetic prices, works offline
"""
from __future__ import annotations
import argparse
import logging
import os
from pathlib import Path
import gradio as gr
from bbca import predict
from bbca.data import DataError
from bbca.model import MAX_HORIZON
from ui import chart, render
from ui.theme import FONTS_HEAD, build_theme
log = logging.getLogger("bbca")
HERE = Path(__file__).parent
DEMO = os.environ.get("BBCA_DEMO") == "1"
HEADER = """
BBCA.JK ยท SARIMA(0,1,0)(2,1,0)[5]
Where does BBCA close next?
Each press pulls the latest prices, refits the model and forecasts the next
sessions. While the market is open it can start from the running price, so the forecast
moves with the day.
"""
def run(horizon: float, use_live: bool, run_no: int):
run_no = int(run_no) + 1
try:
horizon = int(min(max(int(horizon), 1), MAX_HORIZON))
pred = predict(horizon, use_live=bool(use_live), demo=DEMO)
except DataError as exc:
return render.error(str(exc)), chart.empty(), "", "", "", run_no
except Exception:
log.exception("forecast failed")
return (render.error("Something went wrong while fitting the model. Try again."),
chart.empty(), "", "", "", run_no)
return (
render.board(pred, run_no),
chart.build(pred),
render.table(pred.forecast),
render.backtest(pred.backtest),
render.status(pred),
run_no,
)
def build_ui() -> gr.Blocks:
with gr.Blocks(title="BBCA forecast desk") as ui:
runs = gr.State(0)
gr.HTML(HEADER)
with gr.Row(elem_id="bb-controls"):
horizon = gr.Slider(1, MAX_HORIZON, value=1, step=1, label="Sessions ahead", scale=3)
use_live = gr.Checkbox(value=True, label="Use today's running price while the market is open", scale=3)
go = gr.Button("Get fresh forecast", elem_id="bb-go", scale=2)
board = gr.HTML(render.placeholder())
plot = gr.Plot(chart.empty(), elem_id="bb-chart", show_label=False)
with gr.Row(equal_height=True, elem_id="bb-bottom"):
table = gr.HTML()
check = gr.HTML()
status = gr.HTML()
go.click(run, [horizon, use_live, runs], [board, plot, table, check, status, runs],
concurrency_limit=2)
return ui
def main() -> None:
global DEMO
parser = argparse.ArgumentParser(description="BBCA forecast desk")
parser.add_argument("--demo", action="store_true", help="use synthetic prices (offline)")
parser.add_argument("--port", type=int, default=int(os.environ.get("GRADIO_SERVER_PORT", 7860)))
# Local runs stay on 127.0.0.1. Hosting (the HF Space) sets GRADIO_SERVER_NAME=0.0.0.0.
parser.add_argument("--host", default=os.environ.get("GRADIO_SERVER_NAME", "127.0.0.1"))
args = parser.parse_args()
DEMO = DEMO or args.demo
logging.basicConfig(level=logging.INFO)
build_ui().queue(max_size=16).launch(
server_name=args.host,
server_port=args.port,
theme=build_theme(),
css_paths=[HERE / "ui" / "style.css"],
head=FONTS_HEAD,
show_error=False,
)
if __name__ == "__main__":
main()