Currency Market
Requests and displays global currency exchange rates directly on your clock.
| System | AWTRIX NG Scripts |
|---|---|
| Firmware | AWTRIX NG |
| Topic | Miscellaneous |
| Built by | Avemer |
| File | kXNxD0v2fzQw.ax · 7.7 KB |
| Icons | — |
| Published | 11 Aug 2026 · updated 11 Aug 2026 |
| Downloads | 3 |
The script uses the free Frankfurter API to periodically request and display currency exchange rates directly on the watch.
Update 1.1 Configurable Price Precision
- Added a new
PRICE_DECIMALSsetting that allows you to control how many decimal places are displayed next to the currency ticker.
Examples:
PRICE_DECIMALS = 0→USD 1PRICE_DECIMALS = 1→USD 1.2PRICE_DECIMALS = 2→USD 1.23PRICE_DECIMALS = 3→USD 1.234PRICE_DECIMALS = 4→USD 1.2345
Important: This is display formatting, not mathematical rounding. The underlying exchange rate received from the API is not changed. The setting only controls how many decimal places are shown on the watch.
FEATURES
- Supports currency exchange rates using standard 3-letter ISO currency codes. Examples: EUR, USD, GBP, JPY, CNY and others.
- You can choose which currency is used as the BASE currency and which currency is used as the QUOTE currency.
- Displays the currency ticker together with the exchange-rate value.
- Can compare the current rate with the rate returned for the previous calendar day.
- Automatic green/red price coloring based on the previous calendar day's rate.
- Custom ticker and price colors.
- Adjustable ticker length.
- Separated update intervals for the current rate and the previous calendar day's rate for optimization purposes.
- Adjustable spacing between ticker and price.
- Lightweight and suitable for the limited resources of the ULANZI TC001.
- Technically, you can have several such scripts that rotate each other on the clock display and show different currency pairs.
!!! IMPORTANT NOTE - Data Source & Delay !!!
- This script uses the free Frankfurter API to retrieve currency exchange-rate data directly on the watch.
- Frankfurter provides exchange rates based on European Central Bank (ECB) reference rates. These are reference rates, not real-time market prices.
- The underlying reference rates are generally updated once per working day. Therefore, the value returned by the API should be considered a daily reference exchange rate, not a live market rate.
- The watch may request the same available rate multiple times according to the configured update interval. A shorter update interval does not make the underlying exchange rate update more frequently.
- The exact time at which a new reference rate becomes available should not be treated as a real-time market-data timestamp.
- The advantage is that the script runs directly on the watch and does not require Home Assistant, a smart-home server, a PC, or any additional setup. It also works for everyone without an API token or account.
- The script periodically requests the current available rate and, when automatic price coloring is enabled, separately requests the rate for the previous calendar day for comparison.
- The two requests use independent update counters. For example, if:
CURRENT_PRICE_UPDATE_TICKS = 600
and:
PREVIOUS_DAY_UPDATE_TICKS = 610
the requests are controlled by separate counters and are normally scheduled on different loop ticks. - If real-time currency rates are required, the data source can be replaced with a suitable real-time or market-data API, for example:
- A broker's API;
- A foreign exchange market data API;
- Another real-time currency data provider.
- In other words, this script prioritizes simplicity, free access, and standalone operation over real-time exchange rates.
- It is useful for checking approximate daily reference currency rates directly from the watch without opening a phone, browser, or trading application.
- For actual transactions or real-time exchange rates, always verify the rate with your bank, broker, exchange, or other relevant financial service.
Currency Pair Format
- Currency pairs are specified using two 3-letter ISO currency codes:
BASE/QUOTE - The BASE currency is the currency being priced.
- The QUOTE currency is the currency in which the BASE currency is expressed.
For example:BASE = "EUR"QUOTE = "USD"
means: EUR/USD = how many USD are equal to 1 EUR.
If the exchange rate is1.17, this means:
1 EUR = 1.17 USD - In general:
BASE/QUOTE = value of 1 BASE currency expressed in QUOTE currency.
What is displayed on the watch?
- The displayed value is intended to represent the value of 1 BASE currency in the selected QUOTE currency, according to the rate returned by the API.
- When
TICKER_LETTERSis set to1–3, the ticker displayed on the watch represents the BASE currency. - For example:
BASE = "USD"QUOTE = "RUB"
TICKER_LETTERS = 3
displays:USD 80.00
This means: 1 USD = 80 RUB - If
TICKER_LETTERSis set to4or higher, the complete currency pair is displayed:
USD/RUB 80.00
SETTINGS
In code look for section:
====== START OF USER SETTINGS ======
1. Base Currency
self.BASE_CURRENCY = "USD"
Specifies the currency that you want to price.
Use a 3-letter ISO currency code.
Examples:
EUR — Euro
USD — US Dollar
GBP — British Pound
JPY — Japanese Yen
CNY — Chinese Yuan
CHF — Swiss Franc
RUB — Russian Ruble
For example:
self.BASE_CURRENCY = "EUR"
means that the requested exchange rate is based on 1 EUR expressed in the selected QUOTE currency.
2. Quote Currency
self.QUOTE_CURRENCY = "RUB"
Specifies the currency in which the BASE currency is priced.
Examples:
EUR + USD → EUR/USD
USD + EUR → USD/EUR
GBP + USD → GBP/USD
USD + JPY → USD/JPY
EUR + GBP → EUR/GBP
USD + RUB → USD/RUB
For example:
self.BASE_CURRENCY = "USD"
self.QUOTE_CURRENCY = "RUB"
creates: USD/RUB
The exchange-rate value represents how many RUB correspond to 1 USD.
3. Adjust Ticker Length
self.TICKER_LETTERS = 3
Controls how many characters of the currency ticker are displayed.
0 — hide ticker
1 — first letter of BASE currency
2 — first two letters of BASE currency
3 — full BASE currency
4+ — full BASE/QUOTE pair
For example, for EUR/USD:
EUR/USD + 0 → 1.17
EUR/USD + 1 → E 1.17
EUR/USD + 2 → EU 1.17
EUR/USD + 3 → EUR 1.17
EUR/USD + 4 → EUR/USD 1.17
Important: With values 1–3, the displayed ticker represents the BASE currency, not the QUOTE currency.
For example: BASE = USD QUOTE = RUB
TICKER_LETTERS = 3
displays: USD 80.00
not: RUB 80.00
4. Ticker Name Color
self.TICKER_COLOR = 0xFFFFFF
Sets the ticker color using 0xRRGGBB format.
Examples:
0xFFFFFF — White
0xFF0000 — Red
0x00FF00 — Green
0x0000FF — Blue
0xFFFF00 — Yellow
0x00FFFF — Cyan
0xFF00FF — Magenta
5. Price Color Mode
self.PRICE_COLOR_MODE = 2
Controls the price color:
0 — always white
1 — use the custom PRICE_COLOR
2 — automatically use green/red
- When automatic mode (
2) is enabled, the current exchange rate is compared with the rate returned for the previous calendar day.
- Green — current exchange rate is higher than the previous calendar day's rate.
- Red — current exchange rate is lower than the previous calendar day's rate.
- White — current exchange rate is equal to the previous calendar day's rate.
6. Custom Price Color
self.PRICE_COLOR = 0xFFFFFF
Used when:
PRICE_COLOR_MODE = 1
Uses the same 0xRRGGBB color format as the ticker.
See Setting 4 for color examples.
7. Current Price Update Interval
self.CURRENT_PRICE_UPDATE_TICKS = 600
Controls how frequently the script requests the current available exchange rate.
The value is the number of loop ticks between requests.
The counters are initialized to 0, so the first current-rate request is made immediately when the loop starts.
For example:
600 → request the current rate every 600 ticks (Approximately 10 minutes)
610 → request the current rate every 610 ticks (Approximately 10 minutes)
3600 → request the current rate every 3600 ticks (Approximately 60 minutes)
The actual time represented by one loop tick depends on the watch's script execution cycle. Therefore, these values should be understood as tick counts, not guaranteed minutes or seconds.
Because Frankfurter's underlying rates are generally updated only once per working day, there is usually little benefit in requesting the same available rate very frequently.
8. Previous Day Update Interval
self.PREVIOUS_DAY_UPDATE_TICKS = 610
Controls how frequently the script requests the exchange rate for the previous calendar day.
This request is performed only when:
PRICE_COLOR_MODE = 2
The value is the number of loop ticks between requests.
The counter is initialized to 0, so the first previous-day request is also scheduled immediately when automatic price coloring is enabled.
For example:
600 → request the previous day's rate every 600 ticks (Approximately 10 minutes)
610 → request the previous day's rate every 610 ticks (Approximately 10 minutes)
3610 → request the previous day's rate every 3610 ticks (Approximately 60 minutes)
The two update intervals are independent.
For example:
CURRENT_PRICE_UPDATE_TICKS = 600
PREVIOUS_DAY_UPDATE_TICKS = 610
means that the current-rate request and previous-day-rate request use separate counters.
Using slightly different values can help prevent both HTTP requests from being scheduled on the same loop tick.
Important: This setting does not make the historical rate change more frequently. It only controls how often the watch requests the value for the previous calendar day from the API.
Also note that the code requests the previous calendar date, not a calculated "previous working day." If the previous calendar day was a weekend or holiday, the result depends on how the API handles that historical date.
9. Text Gap
self.GAP = 2
Controls the distance in pixels between the ticker and the price.
For example: USD 80.00
The value determines the space between USD and 80.00.
TROUBLESHOOTING
If the script does not work, displays an error, fails to load values, or shows incorrect values:
- Make sure you have entered valid 3-letter ISO currency codes for both
BASE_CURRENCYandQUOTE_CURRENCYand saved the changes (Ctrl+S). - Make sure you have specified the currency pair in the correct order and saved the changes (
Ctrl+S). - Check your internet connection. The script requires internet access to retrieve exchange-rate data.
- Make sure the selected currency pair is supported by the data source.
- The watch may be running out of available memory if you have installed many similar or other scripts at the same time. Consider increasing
CURRENT_PRICE_UPDATE_TICKSandPREVIOUS_DAY_UPDATE_TICKSso the script requests data less frequently.
For example:
600→ less frequent requests
1200→ even less frequent requests
3600→ much less frequent requests - If automatic price coloring is enabled, remember that the script requests the previous calendar day's rate using the
?date=parameter. The previous calendar day is not necessarily a working day. The exact result therefore depends on the historical data available from the API. - The current-rate and previous-day-rate requests are controlled independently. If you configure different tick values, such as:
CURRENT_PRICE_UPDATE_TICKS = 600
PREVIOUS_DAY_UPDATE_TICKS = 610
the requests use separate counters and are normally scheduled on different loop ticks. - Always consider rebooting the watch. Open the watch's web interface by entering its IP address in your browser, go to System → Maintenance → Reboot, and then press "Sure?". A reboot can resolve temporary glitches, memory-related issues, and other unexpected script behavior.
Vibecoded with ChatGPT, polished by Avemer.
P.S. I wrote this script for myself, and I bought the Ulanzi TC001 watch for this idea. Therefore, I tried to optimize the code as much as possible. I didn't test it much thought. I hope that it will work well, but if I find errors, I will try to fix them and update this flow, create similar scripts with different functionality. The code contains explanations, that take some memory to store, if you run into troubles with that, you can always delete them and you can use it as a base for your development. If mistakes occur, do not judge strictly. And I'm a newbie. I hope this script will be useful to someone.
kXNxD0v2fzQw.ax
# @name Currency Market
# @desc Global currency exchange rate display
# @author Avemer
# @version 1.1
class CurrencyMarket
# USER VARIABLES
var BASE_CURRENCY
var QUOTE_CURRENCY
var TICKER_LETTERS
var PRICE_DECIMALS
var TICKER_COLOR
var PRICE_COLOR_MODE
var PRICE_COLOR
var CURRENT_PRICE_UPDATE_TICKS
var PREVIOUS_DAY_UPDATE_TICKS
var GAP
# INTERNAL VARIABLES
var result
var oldPrice
var previousDayPrice
var currentPriceTicks
var previousDayTicks
var color
var displayTICKER
# ================ START OF USER SETTINGS ================
def init()
# BASE CURRENCY
# Use a 3-letter ISO currency code.
# Examples:
# EUR, USD, GBP, JPY, CNY, CHF, RUB
self.BASE_CURRENCY = "EUR"
# QUOTE CURRENCY
# The currency in which the BASE currency is priced.
# Examples:
# EUR + USD = EUR/USD
# GBP + USD = GBP/USD
self.QUOTE_CURRENCY = "USD"
# NUMBER OF TICKER LETTERS DISPLAYED
# 0 = hide ticker
# 1 = first letter of BASE currency
# 2 = first two letters of BASE currency
# 3 = full BASE currency
# 4+ = full BASE/QUOTE pair
self.TICKER_LETTERS = 3
# NUMBER OF PRICE DECIMAL PLACES
# Controls how many digits are displayed
# after the decimal point.
# 0 = no decimal places
# 1 = one decimal place
# 2 = two decimal places
# 3 = three decimal places
# 4 = four decimal places
# Examples:
# PRICE_DECIMALS = 0
# USD 1
# PRICE_DECIMALS = 1
# USD 0.9
# PRICE_DECIMALS = 2
# USD 0.86
# PRICE_DECIMALS = 3
# USD 0.865
# PRICE_DECIMALS = 4
# USD 0.8650
self.PRICE_DECIMALS = 3
# TICKER COLOR
# Format: 0xRRGGBB
self.TICKER_COLOR = 0xFFFFFF
# PRICE COLOR MODE
# 0 = white
# 1 = custom PRICE_COLOR
# 2 = automatic green/red comparison
# Automatic mode compares the current rate
# with the previous calendar day's rate.
self.PRICE_COLOR_MODE = 2
# CUSTOM PRICE COLOR
# Used when PRICE_COLOR_MODE = 1.
self.PRICE_COLOR = 0xFFFFFF
# UPDATE INTERVALS
# CURRENT PRICE UPDATE
# Number of loop ticks between requests
# for the current exchange rate.
# The first request is made immediately
# when the script starts.
self.CURRENT_PRICE_UPDATE_TICKS = 600
# PREVIOUS DAY PRICE UPDATE
# Number of loop ticks between requests
# for the previous calendar day's exchange rate.
# This request is used only when
# PRICE_COLOR_MODE = 2.
self.PREVIOUS_DAY_UPDATE_TICKS = 610
# DISTANCE BETWEEN TICKER AND PRICE
self.GAP = 2
# ================ END OF USER SETTINGS ================
# INITIAL VALUES
self.result = "..."
self.oldPrice = 0
self.previousDayPrice = 0
# Start counters at zero.
# Because the loop decreases the counter
# before checking it, the first requests
# are made immediately.
self.currentPriceTicks = 0
self.previousDayTicks = 0
self.color = 0xFFFFFF
self.displayTICKER = ""
# CREATE DISPLAYED TICKER
if self.TICKER_LETTERS <= 0
self.displayTICKER = ""
elif self.TICKER_LETTERS == 1
self.displayTICKER = self.BASE_CURRENCY[0]
elif self.TICKER_LETTERS == 2
self.displayTICKER = self.BASE_CURRENCY[0] + self.BASE_CURRENCY[1]
elif self.TICKER_LETTERS == 3
self.displayTICKER = self.BASE_CURRENCY
else
self.displayTICKER = self.BASE_CURRENCY + "/" + self.QUOTE_CURRENCY
end
end
# GET PREVIOUS CALENDAR DATE
def get_previous_date()
var y = year()
var m = month()
var d = day()
d -= 1
if d <= 0
m -= 1
if m <= 0
m = 12
y -= 1
end
if m == 1
d = 31
elif m == 2
d = 28
elif m == 3
d = 31
elif m == 4
d = 30
elif m == 5
d = 31
elif m == 6
d = 30
elif m == 7
d = 31
elif m == 8
d = 31
elif m == 9
d = 30
elif m == 10
d = 31
elif m == 11
d = 30
else
d = 31
end
end
# LEAP YEAR CORRECTION
if m == 2
if d == 28
if y % 4 == 0
d = 29
end
end
end
var ms = str(m)
var ds = str(d)
if m < 10
ms = "0" + ms
end
if d < 10
ds = "0" + ds
end
return str(y) + "-" + ms + "-" + ds
end
# APPLY PRICE COLOR
def update_color()
if self.PRICE_COLOR_MODE == 0
self.color = 0xFFFFFF
return
end
if self.PRICE_COLOR_MODE == 1
self.color = self.PRICE_COLOR
return
end
if self.previousDayPrice <= 0
return
end
if self.oldPrice > self.previousDayPrice
self.color = 0x00FF00
elif self.oldPrice < self.previousDayPrice
self.color = 0xFF0000
else
self.color = 0xFFFFFF
end
end
# FORMAT CURRENT PRICE
# The script uses regular expressions to
# extract exactly the requested number of
# decimal places from the API response.
# This avoids unsupported string functions
# such as len() and split().
def format_price(w)
var p
# 0 DECIMAL PLACES
if self.PRICE_DECIMALS <= 0
p = re.search(
"[0-9]+",
w
)
if p == nil
return "..."
end
return p[0]
end
# 1 DECIMAL PLACE
if self.PRICE_DECIMALS == 1
p = re.search(
"[0-9]+[.][0-9]",
w
)
if p == nil
return "..."
end
return p[0]
end
# 2 DECIMAL PLACES
if self.PRICE_DECIMALS == 2
p = re.search(
"[0-9]+[.][0-9][0-9]",
w
)
if p == nil
return "..."
end
return p[0]
end
# 3 DECIMAL PLACES
if self.PRICE_DECIMALS == 3
p = re.search(
"[0-9]+[.][0-9][0-9][0-9]",
w
)
if p == nil
return "..."
end
return p[0]
end
# 4 DECIMAL PLACES
if self.PRICE_DECIMALS == 4
p = re.search(
"[0-9]+[.][0-9][0-9][0-9][0-9]",
w
)
if p == nil
return "..."
end
return p[0]
end
# 5 OR MORE DECIMAL PLACES
# Keep the original API value.
p = re.search(
"[0-9]+[.][0-9]+",
w
)
if p == nil
return "..."
end
return p[0]
end
# CURRENT PRICE RESPONSE
def on_price(w, st)
if w == nil
return
end
# Find the word "rate" in the response.
var m = re.search(
"rate",
w
)
if m == nil
return
end
# Extract the complete numeric rate
# for calculations and color comparison.
var p = re.search(
"[0-9]+[.][0-9]+",
w
)
if p == nil
return
end
var newPrice = num(p[0])
if newPrice == nil
return
end
# Store full numeric value internally.
self.oldPrice = newPrice
# Format only the displayed value.
self.result = self.format_price(w)
# Update price color.
self.update_color()
end
# PREVIOUS CALENDAR DAY RESPONSE
def on_previous_day(w, st)
if w == nil
return
end
# Find the word "rate" in the response.
var m = re.search(
"rate",
w
)
if m == nil
return
end
# Extract the numeric rate.
var p = re.search(
"[0-9]+[.][0-9]+",
w
)
if p == nil
return
end
var newPrice = num(p[0])
if newPrice == nil
return
end
self.previousDayPrice = newPrice
self.update_color()
end
# MAIN LOOP
def loop()
#CURRENT PRICE REQUEST
self.currentPriceTicks -= 1
if self.currentPriceTicks <= 0
self.currentPriceTicks = self.CURRENT_PRICE_UPDATE_TICKS
http.get(
"https://api.frankfurter.dev/v2/rate/" + self.BASE_CURRENCY + "/" + self.QUOTE_CURRENCY,
/ b, st -> self.on_price(b, st)
)
end
#PREVIOUS DAY REQUEST
if self.PRICE_COLOR_MODE == 2
self.previousDayTicks -= 1
if self.previousDayTicks <= 0
self.previousDayTicks = self.PREVIOUS_DAY_UPDATE_TICKS
var previousDate = self.get_previous_date()
http.get(
"https://api.frankfurter.dev/v2/rate/" + self.BASE_CURRENCY + "/" + self.QUOTE_CURRENCY + "?date=" + previousDate,
/ b, st -> self.on_previous_day(b, st)
)
end
end
end
# DRAW
def draw()
clear()
var tickerWidth = text_ink_width(self.displayTICKER)
var priceWidth = text_ink_width(self.result)
var totalWidth = tickerWidth + self.GAP + priceWidth
var x = int((width() - totalWidth) / 2)
if self.TICKER_LETTERS > 0
text(
x,
7,
self.displayTICKER,
self.TICKER_COLOR
)
end
text(
x + tickerWidth + self.GAP,
7,
self.result,
self.color
)
end
def on_button(btn)
end
end
return CurrencyMarket()
Install it on your AWTRIX NG
Your browser writes the script straight to the device on your network. It joins the app rotation right away.
Some browsers refuse to talk to a device on your network from a public page. Download the flow and paste it into the Scripts tab of your device, or install it from a terminal:
More flows for AWTRIX NG Scripts or Miscellaneous
Something wrong with this flow? Report it.