DEV Community

Cover image for How I built a trade analytics dashboard with Streamlit and Plotly — and turned it into a product
Sujal Makwana
Sujal Makwana

Posted on AI-assisted

How I built a trade analytics dashboard with Streamlit and Plotly — and turned it into a product

I'm an AI/ML student and I build Streamlit dashboards as a freelancer on the side. A few weeks ago I was working on a trade analytics project and realized I was writing the same landed cost logic, currency conversion and chart code from scratch — again.

So I packaged what I built into a proper boilerplate. Here's how it works and what I learned.

What the dashboard does

LandedIQ calculates trade unit economics instantly:

Landed cost = base price + import duty + freight + insurance
Net profit and margin at your target sale price
Live USD ↔ INR via ExchangeRate-API (with offline fallback)
CSV uploader — inject your own product database with zero code changes
Cost breakdown donut, price volatility vs break-even chart, 5-year macro trends
Tech stack
Python 3.11
Streamlit >= 1.35
Plotly >= 5.18
Pandas >= 2.0
Requests

One thing I learned the hard way — don't use streamlit-echarts. It breaks on Python 3.13 and the error is cryptic. Plotly works everywhere out of the box.

Project structure

Instead of one giant app.py I split everything into modules:

├── app.py # entry point only (~80 lines)
├── components/
│ ├── charts.py # all Plotly chart builders
│ ├── control_panel.py # UI inputs and CSV ingestion
│ └── ui.py # CSS injection and theme tokens
├── data/
│ ├── products.py # default HSN product database
│ └── sample_products.csv
└── utils/
├── calculations.py # pure trade math functions
└── currency.py # API fetching with fallback

This made the code way easier to maintain and test. Each chart is a pure function that returns a Plotly figure — no Streamlit imports, fully testable in isolation.

The currency caching trick

The exchange rate API gets called on every rerender if you're not careful. One line fixes it:

python
@st.cache_data(ttl=3600)
def fetch_exchange_rate(base: str = "USD", target: str = "INR") -> float:
try:
url = f"https://api.exchangerate-api.com/v4/latest/{base}"
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()["rates"][target]
except Exception:
return 83.50 # fallback

Cache for 1 hour. If the API is down, return a hardcoded fallback. Simple and reliable.

Deploying on Railway

Railway auto-detects Python but does NOT auto-detect Streamlit. You need to set the start command manually:

streamlit run app.py --server.port $PORT --server.address 0.0.0.0

Without --server.address 0.0.0.0 it only binds to localhost and Railway can't reach it.

What I turned it into

After building it I cleaned up the code, added a PDF setup guide, wrote proper docs and listed it as a boilerplate on Gumroad and Contra.

Live demo: landediq-production.up.railway.app

The full boilerplate is available if you want to skip the setup and jump straight to customising it for your own use case.

What I'd do differently
Start with Plotly, not ECharts
Set up the module structure from day one instead of refactoring later
Write the .env.example before you forget which API keys the project uses

If you're building something similar or have questions about the architecture, drop a comment. Happy to help.

LandedIQ

Top comments (0)