<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sujal Makwana</title>
    <description>The latest articles on DEV Community by Sujal Makwana (@sujal_makwana_efe39a648c2).</description>
    <link>https://dev.to/sujal_makwana_efe39a648c2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4145311%2Fa34e7597-7fa7-44c1-b89b-2468674ccb01.png</url>
      <title>DEV Community: Sujal Makwana</title>
      <link>https://dev.to/sujal_makwana_efe39a648c2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sujal_makwana_efe39a648c2"/>
    <language>en</language>
    <item>
      <title>How I built a trade analytics dashboard with Streamlit and Plotly — and turned it into a product</title>
      <dc:creator>Sujal Makwana</dc:creator>
      <pubDate>Sun, 27 Sep 2026 09:57:26 +0000</pubDate>
      <link>https://dev.to/sujal_makwana_efe39a648c2/how-i-built-a-trade-analytics-dashboard-with-streamlit-and-plotly-and-turned-it-into-a-product-4fbj</link>
      <guid>https://dev.to/sujal_makwana_efe39a648c2/how-i-built-a-trade-analytics-dashboard-with-streamlit-and-plotly-and-turned-it-into-a-product-4fbj</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So I packaged what I built into a proper boilerplate. Here's how it works and what I learned.&lt;/p&gt;

&lt;p&gt;What the dashboard does&lt;/p&gt;

&lt;p&gt;LandedIQ calculates trade unit economics instantly:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Project structure&lt;/p&gt;

&lt;p&gt;Instead of one giant app.py I split everything into modules:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The currency caching trick&lt;/p&gt;

&lt;p&gt;The exchange rate API gets called on every rerender if you're not careful. One line fixes it:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
&lt;a class="mentioned-user" href="https://dev.to/st"&gt;@st&lt;/a&gt;.cache_data(ttl=3600)&lt;br&gt;
def fetch_exchange_rate(base: str = "USD", target: str = "INR") -&amp;gt; float:&lt;br&gt;
    try:&lt;br&gt;
        url = f"&lt;a href="https://api.exchangerate-api.com/v4/latest/%7Bbase%7D" rel="noopener noreferrer"&gt;https://api.exchangerate-api.com/v4/latest/{base}&lt;/a&gt;"&lt;br&gt;
        response = requests.get(url, timeout=5)&lt;br&gt;
        response.raise_for_status()&lt;br&gt;
        return response.json()["rates"][target]&lt;br&gt;
    except Exception:&lt;br&gt;
        return 83.50  # fallback&lt;/p&gt;

&lt;p&gt;Cache for 1 hour. If the API is down, return a hardcoded fallback. Simple and reliable.&lt;/p&gt;

&lt;p&gt;Deploying on Railway&lt;/p&gt;

&lt;p&gt;Railway auto-detects Python but does NOT auto-detect Streamlit. You need to set the start command manually:&lt;/p&gt;

&lt;p&gt;streamlit run app.py --server.port $PORT --server.address 0.0.0.0&lt;/p&gt;

&lt;p&gt;Without --server.address 0.0.0.0 it only binds to localhost and Railway can't reach it.&lt;/p&gt;

&lt;p&gt;What I turned it into&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Live demo: landediq-production.up.railway.app&lt;/p&gt;

&lt;p&gt;The full boilerplate is available if you want to skip the setup and jump straight to customising it for your own use case.&lt;/p&gt;

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

&lt;p&gt;If you're building something similar or have questions about the architecture, drop a comment. Happy to help.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sujalicious5278.gumroad.com/l/LandedIQ" rel="noopener noreferrer"&gt;LandedIQ&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>streamlit</category>
      <category>datascience</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
