Skip to main content

tgju-rates

Live Iranian rial exchange rates from tgju.org in your terminal.

  • Updates in place: only the rows whose prices changed are redrawn, so the screen doesn't flicker.
  • Prices in rial and toman, daily change, low and high.
  • ▲/▼ markers on prices that just moved, and a sparkline of each currency's recent changes.
  • A short trading-screen animation on every price change: flash, glow, rolling digits or both. Or none.
  • A watchlist, and price alerts with desktop notifications.
  • Toman mode (--toman) and JSON output (--json) for scripts.
  • A settings file for the options you always use.
  • Price history saved to a local SQLite file, so sparklines survive restarts and past prices can be listed.
  • No dependencies. Only the Python standard library is used.

tgju-rates live view with a savings panel, price changes and trend sparklines

Installation

Requires Python 3.9 or newer.

pipx install tgju-rates    # or: pip install tgju-rates

On distributions that mark the system Python as externally managed (recent Ubuntu, Debian, Fedora), plain pip install refuses to run. Use pipx, or a virtual environment.

To install from a clone instead:

git clone https://github.com/moxart/tgju-rates.git
cd tgju-rates
pipx install .

You can also run it from the source tree without installing: PYTHONPATH=src python3 -m tgju_rates.

Usage

tgju-rates                       # live, refresh every 10 seconds
tgju-rates -i 5                  # refresh every 5 seconds (minimum 3)
tgju-rates --once                # print the table once and exit
tgju-rates --persian             # add a column with the Persian names
tgju-rates --animation roll      # flash (default), glow, roll, board or off
tgju-rates --watch usd,eur,gbp   # show only these currencies, in this order
tgju-rates --market coin,gold    # gold coins and gold instead of currencies (currency, coin, gold, crypto, all)
tgju-rates --watch usd,emami,btc # mix markets; each code's market is fetched as needed
tgju-rates --dashboard           # currencies, coins, gold and crypto as panels on one screen
tgju-rates --alert "usd>2600000" --alert "eur<=2,850,000"
tgju-rates --toman               # prices in toman; --alert limits are read as toman too
tgju-rates --hold usd=1200@2,450,000 --hold eur=300   # what your savings are worth
tgju-rates --alert "total>5,000,000,000"              # alert on your savings' total
tgju-rates --once --json         # one JSON object; live mode with --json prints one per update (JSON Lines)
tgju-rates --history usd         # saved USD prices from the last day
tgju-rates --history eur --days 7 --toman
tgju-rates --history usd --days 30 --chart   # a chart and a daily open/low/high/close table
tgju-rates --history usd --days 30 --csv > usd.csv
tgju-rates --history usd --chart --jalali    # dates in the Persian calendar
tgju-rates --convert "250 usd"                # 250 USD in rial and toman
tgju-rates --convert "50,000,000 toman to btc"
tgju-rates --jewelry "12.5g wage 18%"         # what an 18k gold piece should cost
tgju-rates --doctor              # check that tgju.org still works with this program

tgju-rates --help lists every option, grouped, with examples.

Currency codes are the site's codes in lower case (eur, gbp, aed, ...). usd is accepted for the US dollar. An unknown code prints the list of valid ones.

Gold coins, gold and crypto

--market picks what the table shows: currency (the default), coin, gold, crypto, or a comma-separated mix, or all. Their codes work everywhere a currency code does: --watch, --alert, --hold, --history and --convert.

Market Codes
coin emami (Emami), bahar (Bahar Azadi), nim (half), rob (quarter), gerami (gram coin)
gold gold18, gold24 (per gram), mesghal (mithqal), usedgold, silver (999), silver925
crypto btc, eth, usdt, bnb, xrp, usdc, sol, trx, doge, ada, ton, link, xlm, ltc, bch, avax, dot, xmr, shib

All prices are in rial (toman with --toman), so a coin or a gram of gold can go in your savings: --hold emami=2@2,400,000,000 --hold gold18=15.5. tgju-rates --help lists the codes too.

Dashboard

--dashboard shows one panel per market (currencies, gold coins, gold & silver, crypto) with the main codes of each: price, today's change in percent and, when live, the trend. The panels go side by side as far as the terminal is wide, so four panels sit 2×2 at about 130 columns and in a single row at about 150. --watch picks your own codes, which are grouped into panels by market, and --market limits which panels appear:

tgju-rates --dashboard --toman
tgju-rates --dashboard --watch usd,eur,try,emami,rob,btc,usdt
tgju-rates --dashboard --market coin,gold

The keys, animations, alerts and savings panel work the same as in the table view.

Converter

--convert turns an amount into rial and toman, or into another code, using live prices, then exits:

$ tgju-rates --convert "250 usd eur"
250 USD = 221.38 EUR
  1 USD (US Dollar) = 2,679,000 rial
  1 EUR (Euro) = 3,025,400 rial

Write AMOUNT CODE, AMOUNT CODE CODE, or put rial/toman on either side ("50,000,000 toman to btc"); to and in are optional. --json prints the result as JSON. Yen is priced per 100 on the site, and the converter accounts for that. If the feed can't be reached, the last prices saved in the history file are used and the output says so.

Alert limits are in rial. An alert fires once when its condition becomes true and fires again only after the condition has stopped holding in between. Fired alerts are listed above the table and sent as a desktop notification when notify-send is available. Alerts work on any currency, including ones not in --watch.

To turn alerts off, press m while it runs: no notifications, and the ALERT lines go away, until you press m again. Alerts that come true while muted aren't replayed when you unmute. --no-alerts starts without any alerts, including the ones in the settings file.

Jewelry calculator

--jewelry works out what a piece of 18k gold jewelry should cost at the live price of a gram of 18k gold, using the formula gold shops use:

  • gold = weight × price per gram
  • making charge (اجرت) = a percent of the gold
  • seller's profit = a percent of gold plus making charge (7% unless you give profit)
  • VAT = a percent of making charge plus profit, not of the gold (10% unless you give tax)
tgju-rates --jewelry "12.5g wage 18%"
tgju-rates --jewelry "8 grams wage 20% profit 5%" --toman
tgju-rates --jewelry "3.2g wage 15% tax 9%" --json

Each part is shown in rial and toman. When the feed can't be reached, the last saved gold price is used and a note says so.

Your savings

List the currencies you hold, and a panel above the table shows what each is worth now, how much it has gained since you bought it, and today's move, with a TOTAL row and a sparkline of your total:

   YOUR SAVINGS  AMOUNT   WORTH (RIAL)  SINCE BOUGHT    TODAY
   USD            1,200  3,101,580,000     ▲ +26.6%   ▲ +0.8%
   EUR              300    877,050,000                ▼ -0.5%
   TOTAL                 3,978,630,000     ▲ +26.6%   ▲ +0.5%  ▁▃▂▆█

Write each holding as code=amount, or code=amount@price with the price you paid per unit. Put them in ~/.config/tgju-rates/holdings.txt (or --holdings PATH), one per line:

# code=amount@price paid per unit
usd=1200@2,450,000
eur=300                 # no price: shows worth and today's move, but no gain
gbp=0.5@330,000 toman

or pass them with --hold, which replaces the file's line for the same currency. Prices in the file are rial unless you write toman after them; --hold prices follow --toman, like --alert limits. SINCE BOUGHT on the TOTAL row covers only the holdings with a price. --alert "total>LIMIT" fires when your total crosses the limit, and --json adds a holdings object.

Settings file

Options you'd otherwise type every time can go in ~/.config/tgju-rates/config.ini (or $XDG_CONFIG_HOME/tgju-rates/, or any file with --config PATH). Name each option like the long option without its dashes:

[defaults]
toman = yes
watch = usd,eur,gbp,emami,gold18,btc
interval = 15
animation = roll
alert = usd>2,700,000
        total>5,000,000,000

The file can set interval, toman, jalali, persian, dashboard, animation, market, watch, alert, color, days, db, holdings and no-record. Flags take yes or no. A repeatable option like alert takes one value per line.

The command line wins over the file: --no-toman turns off a toman = yes, and -i 5 replaces the interval. --alert values on the command line are added to the file's. A value the program can't read stops it with the file, option and reason. --doctor also checks the file.

Colours

Colours are on in a terminal and off when the output is piped. --color always or --color never overrides that, and so do the NO_COLOR and FORCE_COLOR environment variables.

The live screen

The top line shows the connection state: ● LIVE while prices come in, ● OFFLINE after a failed poll (with the error on the next line, while it retries), and ❚❚ PAUSED. Then the time of the last update, the interval, the unit, and how many rates and alerts there are. The bottom line lists the keys that work in the current view; press ? for all of them.

Keys

In live mode in a terminal, single keys change the view without restarting:

Key Does
↑ ↓ select a row (in the table or the dashboard)
Enter open the selected row's details: price, change, low/high, its alerts, a chart and the last 7 days
d in the details, show the last 1, 7 or 30 days
t switch between toman and rial
s sort: site order, biggest move today, highest price
/ filter by code or English name as you type; Enter keeps it, Esc clears it
c convert, e.g. 250 usd eur; the result updates as you type and with each new price
a next price animation: flash, glow, roll, board, off
Esc leave the details; otherwise clear the filter, the converter and the selection
m mute alerts: no notifications and the ALERT lines are cleared; m again unmutes
p pause and resume updates
? show or hide the help screen
q quit (Ctrl+C works too)

t only changes the display. --alert limits stay in the unit they were given in.

The details chart comes from the history file, so it's empty with --no-record and fills in as prices are saved.

c takes the same input as --convert but uses the prices already on screen, so it works only for loaded markets (--market coin for emami, for example). Enter keeps the line above the table; c edits it again.

Animations

When a price changes, its price and second-unit cells play a short effect (1.5 seconds) in green for a rise or red for a fall:

--animation Effect
flash the cell lights up and fades back into the row (default)
glow the digits flare white, then cool from bright green or red to normal
roll only the digits that changed spin and land on the new value, left to right
board roll and flash together
off no animation; the ▲/▼ marker and coloured price still show the move

Press a to try them while it runs. Animations only play in a colour terminal; piped output and --json never animate.

Piped output (tgju-rates > rates.log) has no colours, ignores keys and appends a full table on every update.

JSON

--json prints numbers instead of formatted text: price, change, low and high are integers in the unit given by "unit" ("rial", or "toman" with --toman), and change_percent is a float. Fields the site didn't fill are null. Alerts that fired on that update are listed under "alerts".

tgju-rates --once --json --watch usd | jq '.rates[0].price'
tgju-rates --json --watch usd,eur > rates.jsonl

History

Every run saves prices to ~/.local/share/tgju-rates/history.db (or $XDG_DATA_HOME/tgju-rates/). A row is written only when a price changes. Live mode starts its TREND sparklines from this file, and --history CODE lists what was saved. Add --chart for a chart over the --days window with a table of each day's open, low, high and close, --csv for a spreadsheet, or --json for scripts. Use --db PATH for a different file and --no-record to not save anything. If the file can't be opened, the program warns and runs without it.

USD (US Dollar), last 7 day(s), in rial

2,685,000 ┤                                                    ███▂▂
          │                                                  ▇▇█████
2,634,825 ┤                                                  ███████
          │                                                  ███████
2,584,650 ┤                                         ▁▁▁▁▁▁▁▁▁███████
          └─────────────────────────────────────────────────────────
           2026-09-27 01:32                         2026-10-04 01:32

DATE             OPEN        LOW       HIGH      CLOSE           CHANGE
2026-10-02  2,584,650  2,584,650  2,584,650  2,584,650
2026-10-03  2,669,700  2,666,750  2,685,200  2,679,000  +94,350 (+3.7%)

Prices are only saved while the program runs, so gaps in the chart are times it wasn't running.

--jalali shows dates in the Persian calendar (1405/07/12 instead of 2026-10-04) in --history, --chart, the live status line and the details view. With --csv it adds a jalali_time column and keeps the ISO time column for spreadsheets. JSON output keeps ISO times.

Shell completion

--completion bash|zsh|fish prints a completion script for options and codes:

tgju-rates --completion bash > ~/.local/share/bash-completion/completions/tgju-rates
tgju-rates --completion fish > ~/.config/fish/completions/tgju-rates.fish
echo 'eval "$(tgju-rates --completion zsh)"' >> ~/.zshrc   # after compinit

Troubleshooting

tgju-rates --doctor checks each part: the currency page parses, the feed answers, every currency and every coin, gold and crypto entry is in the feed, and the history and holdings files can be read. It exits with status 1 if anything failed, which makes its output a good start for a bug report.

If the program stops with "unexpected error", run the same command with --debug to see the full traceback, and please open an issue with it and the --doctor output.

Exit statuses: 0 success, 1 an error (no connection and nothing saved, unreadable file, ...), 2 a mistake in the options or the settings file, 130 stopped with Ctrl+C.

How it works

  1. At startup, the currency page is scraped once to find which currencies to show and their Persian names. Coins, gold and crypto aren't scraped: the feed already has them, and markets.py lists which entries to show.
  2. While running, prices are polled from https://call1.tgju.org/ajax.json, the feed the site uses for its own live updates. A unique query string gets around its 5-minute CDN cache. After a failed poll the wait doubles each time (up to 2 minutes) and goes back to normal on the next success.

If tgju.org changes its page markup, the scraper is the part that breaks. The program then starts from the list of currencies saved in the history file, says so above the table, and keeps updating prices from the feed if that still works. With nothing saved, it exits with the error. --doctor shows which part broke.

Project layout

src/tgju_rates/
├── cli.py         argument parsing and the entry point
├── config.py      the settings file
├── app.py         --once mode and the live polling loop
├── source.py      scraping the page and reading the JSON feed
├── markets.py     the coin, gold and crypto entries taken from the feed
├── currencies.py  currency codes, English names, rial/toman formatting
├── convert.py     --convert
├── jewelry.py     --jewelry
├── chart.py       --history --chart
├── dates.py       Gregorian or Jalali dates for display
├── doctor.py      --doctor
├── completion.py  shell completion scripts
├── tracking.py    price moves between polls and trend history
├── history.py     the SQLite price history
├── export.py      JSON output
├── alerts.py      alert rules and desktop notifications
├── controls.py    live-mode keys, key bar and help screen
├── table.py       rendering the table, sparklines and colours
├── dashboard.py   --dashboard: one panel per market, laid out in a grid
├── screen.py      flicker-free in-place terminal updates
└── ansi.py        terminal escape codes and the colour palette
tests/             unit tests (offline, no network needed)

Development

python3 -m venv .venv && . .venv/bin/activate
pip install -e . ruff
python -m unittest discover -s tests   # tests
ruff check . && ruff format --check .  # lint and formatting

See CONTRIBUTING.md for the conventions and how releases are made, and CHANGELOG.md for what changed.

A currency the site adds later shows - as its name until it gets an entry in ENGLISH_NAMES in currencies.py.

Disclaimer

This is an unofficial tool and is not affiliated with tgju.org. All data belongs to tgju.org. Please keep the refresh interval reasonable.

License

MIT

Metadata

Release files for tgju-rates 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tgju-rates 0.2.0
File Size Uploaded Provenance
tgju_rates-0.2.0.tar.gz 83.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for tgju-rates 0.2.0
File Interpreter ABI Platform
tgju_rates-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 149.3 kB

Release files / tgju_rates-0.2.0.tar.gz

Download URL tgju_rates-0.2.0.tar.gz
Size 83.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1cd6436f39d15c43e7baccadfa75019e23ea8af28577ecd793c69828cdeb9f2d
BLAKE2b-256 checksum
How to use checksums
ca0794d8b0a1221bc0c15f36f7435ac64c09286f16b544ad1795df49eb18ee8c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / tgju_rates-0.2.0-py3-none-any.whl

Download URL tgju_rates-0.2.0-py3-none-any.whl
Size 65.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
216947cf5163af4f5404eb7d6758326ecd8734862a055d03b45d9e6e48c99237
BLAKE2b-256 checksum
How to use checksums
3b09e84707dd4525778abaf7050f8cd132e2131a6b9cd02fae96bb3e6c4eb55b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for tgju-rates 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for tgju_rates-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page