ds@mcpanalytics.aiEmail your data and your question and get an answer about your own data, with the method shown.Email your data and question. Get an answer.

How to Use International Sales & Currency in Etsy: Step-by-Step Tutorial

Updated October 2026

Category: Etsy Analytics | Reading Time: 12 minutes

Introduction to International Sales & Currency Analysis for Etsy Sellers

If you ship outside your home country, your order file mixes currencies. Ten USD, ten GBP and ten EUR do not add up to thirty of anything, so the first rule of this analysis is: never add amounts that are in different currencies. This tutorial shows how to count orders and revenue per currency in the currency they were paid in, and then how to convert to one reporting currency, with the exchange rates you used written down, so that a country ranking can be checked.

All code uses pandas. The figures on this page come from a synthetic data set of 600 orders over 90 days that was generated for this tutorial, with synthetic exchange rates. It is not from a real shop, so your numbers will differ.

Prerequisites and Data Requirements

What You Need From Your Order Data

Exporting Your Etsy Orders

Etsy lets sellers download their sold orders as a CSV from Shop Manager. The menu path, the file name and the column names change over time, and we could not check Etsy's current help pages when updating this tutorial, so treat every Etsy-specific detail here as an assumption and check it against your own file. In particular, open the file and confirm that it has a column for the ship-to country, one for the currency and one for the order total, and check whether the total is in the buyer's currency or in your payout currency. If your file has only one of those, you cannot do the per-currency steps below.

Exchange Rates

You will need an exchange rate for each order's date. This tutorial uses the European Central Bank's euro foreign exchange reference rates: quoted for each currency against the euro and updated each working day at about 16:00 CET. The ECB publishes the reference rates for information purposes only and says that using them for transaction purposes is strongly discouraged, so treat the converted figures as a consistent yardstick for comparing countries, not as accounting values. For figures that match what Etsy paid out to you, use your payment account statements.

Step-by-Step Guide to Analyzing International Sales & Currency

Step 1: Load the Orders

The block below builds the synthetic orders. To use your own file, replace the generator with pd.read_csv and rename your columns to order_id, sale_date, ship_country, currency and order_total. Convert sale_date to a date with pd.to_datetime, and make sure the currency column holds three-letter ISO codes (USD, not $).

import numpy as np
import pandas as pd

# Synthetic orders generated for this tutorial. Replace this block with
# orders = pd.read_csv('your_etsy_orders.csv') and rename your columns to match.
rng = np.random.default_rng(7)
n = 600
mix = {'United States': ('USD', 0.50, 44), 'United Kingdom': ('GBP', 0.18, 36),
       'Canada': ('CAD', 0.12, 58), 'Australia': ('AUD', 0.08, 70),
       'Germany': ('EUR', 0.07, 38), 'France': ('EUR', 0.05, 40)}
countries = rng.choice(list(mix), size=n, p=[v[1] for v in mix.values()])
orders = pd.DataFrame({
    'order_id': np.arange(10001, 10001 + n),
    'sale_date': pd.Timestamp('2024-01-01') + pd.to_timedelta(rng.integers(0, 90, n), unit='D'),
    'ship_country': countries,
    'currency': [mix[c][0] for c in countries],
    'order_total': [round(float(rng.gamma(4, mix[c][2] / 4)), 2) for c in countries],
})
orders = orders.sort_values(['sale_date', 'order_id']).reset_index(drop=True)
print(orders.head().to_string(index=False))

Expected output:

 order_id  sale_date   ship_country currency  order_total
    10030 2024-01-01 United Kingdom      GBP        46.90
    10162 2024-01-01        Germany      EUR        25.34
    10532 2024-01-01  United States      USD        51.62
    10556 2024-01-01  United States      USD       128.28
    10019 2024-01-02 United Kingdom      GBP         8.19

Step 2: Count Per Currency, in That Currency

Group by currency and report each currency's own totals. The revenue_local column is in the currency named in the row, so the five revenue figures cannot be added to each other. The order counts and shares of orders can be compared, because an order is one order whatever it was paid in.

# Per currency, in that currency. These amounts are NOT added across currencies.
by_cur = orders.groupby('currency').agg(
    orders=('order_id', 'count'),
    revenue_local=('order_total', 'sum'),
    avg_order_local=('order_total', 'mean'),
).round(2)
by_cur['order_share_pct'] = (100 * by_cur['orders'] / by_cur['orders'].sum()).round(1)
print(by_cur.sort_values('orders', ascending=False))

Expected output:

          orders  revenue_local  avg_order_local  order_share_pct
currency                                                         
USD          288       12528.98            43.50             48.0
GBP          121        4208.53            34.78             20.2
CAD           72        4040.14            56.11             12.0
EUR           68        2567.92            37.76             11.3
AUD           51        3358.17            65.85              8.5

In this data set USD is 48.0% of orders and the four other currencies together are 52.0%. EUR covers two countries (Germany and France), so a currency table is not a country table. Step 3 is needed to rank countries by revenue.

Step 3: Convert to One Reporting Currency

To rank countries by revenue you need every order in one currency. The block below converts each order to USD with the rate from the order's date (the last published rate on or before that date, because the ECB publishes only on working days). It prints how many orders found no rate, which should be zero: if it is not, those orders are silently missing from every total that follows.

# Exchange rates in the layout of the ECB euro reference rates file: units of currency per 1 EUR, one row per day.
# These rates are synthetic. For your own file, download the full history CSV (eurofxref-hist.zip) from the ECB page.
days = pd.bdate_range('2023-12-25', '2024-03-31')
base = {'USD': 1.09, 'GBP': 0.86, 'CAD': 1.46, 'AUD': 1.64}
rates = pd.DataFrame({'Date': days})
for cur, r0 in base.items():
    rates[cur] = r0 * np.exp(np.cumsum(rng.normal(0, 0.003, len(days))))
rates['EUR'] = 1.0

# The ECB publishes on working days only, so carry the last published rate over weekends.
daily = rates.set_index('Date').reindex(pd.date_range(days[0], orders['sale_date'].max(), freq='D')).ffill()
long = daily.stack()
key = pd.MultiIndex.from_arrays([orders['sale_date'], orders['currency']])
orders['cur_per_eur'] = long.reindex(key).to_numpy()
orders['usd_per_eur'] = daily['USD'].reindex(orders['sale_date']).to_numpy()
orders['order_usd'] = orders['order_total'] / orders['cur_per_eur'] * orders['usd_per_eur']
print('orders without a rate:', int(orders['order_usd'].isna().sum()))
print('total in USD at order-date rates:', round(orders['order_usd'].sum(), 2))

Expected output:

orders without a rate: 0
total in USD at order-date rates: 26219.63

Step 4: Rank Countries

Now group by ship-to country. The last line checks that the country totals add up to the currency totals, which is the reconciliation to run on your own file too: it fails if an order has a country but no currency, or the other way round.

by_country = orders.groupby('ship_country').agg(
    currency=('currency', 'first'),
    orders=('order_id', 'count'),
    revenue_usd=('order_usd', 'sum'),
).sort_values('revenue_usd', ascending=False)
by_country['avg_order_usd'] = (by_country['revenue_usd'] / by_country['orders']).round(2)
by_country['share_pct'] = (100 * by_country['revenue_usd'] / by_country['revenue_usd'].sum()).round(1)
by_country['cumulative_pct'] = by_country['share_pct'].cumsum().round(1)
by_country['revenue_usd'] = by_country['revenue_usd'].round(2)
print(by_country.to_string())
by_currency_usd = orders.groupby('currency')['order_usd'].sum()
print('Sum by country equals sum by currency:', bool(np.isclose(by_country['revenue_usd'].sum(), by_currency_usd.sum(), atol=0.05)))

Expected output:

               currency  orders  revenue_usd  avg_order_usd  share_pct  cumulative_pct
ship_country                                                                          
United States       USD     288     12528.98          43.50       47.8            47.8
United Kingdom      GBP     121      5516.15          45.59       21.0            68.8
Canada              CAD      72      3043.90          42.28       11.6            80.4
Australia           AUD      51      2280.60          44.72        8.7            89.1
Germany             EUR      42      1807.73          43.04        6.9            96.0
France              EUR      26      1042.27          40.09        4.0           100.0
Sum by country equals sum by currency: True

Reading the table: the United States is 47.8% of converted revenue and the top three countries (United States, United Kingdom, Canada) are 80.4% of it, from the cumulative column. The United Kingdom has the highest average order value in this data set (45.59 USD, against 43.50 for the United States). The table does not say why, and with 121 UK orders a difference of a couple of dollars in the average is small next to the spread of individual order values, so do not set prices from it. Germany and France are both paid in EUR; their two revenue figures add up to the EUR figure in Step 5 (1,807.73 + 1,042.27 = 2,850.00).

Step 5: Measure What the Exchange Rate Did to the Converted Figure

A converted total moves when the rate moves, even if buyers paid exactly the same. To see how much, convert the same local amounts twice, once at each order's own date rate and once at the last rate in the file, and compare.

# How much does the exchange rate move the USD figure? Same local amounts, two rates.
last = rates.drop(columns='Date').iloc[-1].astype(float)
orders['order_usd_at_last_rate'] = orders['order_total'] / orders['currency'].map(last) * last['USD']
fx = orders.groupby('currency')[['order_usd', 'order_usd_at_last_rate']].sum().round(2)
fx['difference_usd'] = (fx['order_usd_at_last_rate'] - fx['order_usd']).round(2)
fx['difference_pct'] = (100 * fx['difference_usd'] / fx['order_usd']).round(1)
print(fx)

Expected output:

          order_usd  order_usd_at_last_rate  difference_usd  difference_pct
currency                                                                   
AUD         2280.60                 2328.86           48.26             2.1
CAD         3043.90                 3088.55           44.65             1.5
EUR         2850.00                 2882.34           32.34             1.1
GBP         5516.15                 5649.73          133.58             2.4
USD        12528.98                12528.98            0.00             0.0

The USD row is zero because USD orders do not change when converted to USD. For the other currencies, the difference is 1.1% to 2.4% of the converted figure in this synthetic data, and it depends entirely on the synthetic rate path. It says how sensitive the converted number is to the choice of rate, not what your profit would be: to measure the effect on profit you also need your costs and the rate at which your payments were actually converted.

Step 6: Use the Results Carefully

Some uses that follow from the tables, each of which is a hypothesis to test rather than a conclusion:

Checking Your Own Numbers

Reconcile Order Counts

The number of rows in your file for the period should equal the number of orders you see in Etsy Shop Manager for the same dates, unless you removed cancelled orders on purpose. Check where your own export puts refunds and cancellations before comparing.

Reconcile Revenue

Per currency, the sum of order_total in your analysis should equal the sum of the same column in the file you uploaded. After conversion, expect your total to differ from your payout statements, because Etsy and your payment provider convert at their own rates and on their own dates and deduct fees and refunds. There is no standard tolerance for that gap; it is whatever your statements say it is.

Top Country Sanity Check

If an unexpected country is at the top, look for a few very large orders, a mislabeled country, or a package forwarding address.

Run It on Your Own Etsy Export

Export your orders from Etsy as a CSV and upload it at MCP Analytics. You can group revenue by country and by currency on your file and see the result with the method used. You choose the columns; nothing connects to your Etsy account.

Try it on your data

Next Steps with Etsy Analytics

Troubleshooting Common Issues

Issue 1: Missing or Blank Country Values

Count them before grouping, because groupby drops rows whose key is missing.

print('orders with no ship country:', int(orders['ship_country'].isna().sum()))
print('orders with no currency:', int(orders['currency'].isna().sum()))

If either count is not zero, fix the source file or report those orders as their own line, so that totals still match the file.

Issue 2: Orders Without a Rate

The Step 3 check prints the number of orders with no rate. The usual causes are a date before the first date in your rate file, a currency the ECB does not quote, or a date format that did not parse.

Issue 3: Converted Totals Differ From Your Payouts

Expected: see Step 5 and the ECB caveat above. For figures that reconcile to money received, use the amounts on your payment account statements and compare in the currency you are paid in.

Issue 4: No Clear Geographic Pattern

If revenue is spread thinly across many countries, extend the period, or rank by order count instead of revenue, or group countries into regions. A flat table is a result too.

Conclusion: What This Analysis Does and Does Not Show

You now have a per-currency table that never adds unlike currencies, a country ranking in one reporting currency with the rate source stated, a reconciliation between the two, and a measure of how much the rate choice moves the converted figure. What it does not show is why a market performs as it does, or what would happen if you changed shipping or prices there. Those need a comparison, not a ranking.

Explore more: Etsy Shop Analytics: all tools, tutorials, and guides →

Not sure which plan? Compare plans →

Your turn

Bring your own data and the question you actually need answered.

MCP AnalyticsData science by email Send us your data and your question, and we will reply. ds@mcpanalytics.ai