Hướng dẫnBảng tính

Giá vàng trực tiếp trong Google Sheets

Lấy giá vàng trực tiếp và lịch sử vào Google Sheets, không lưu khóa API, so sánh giá tham chiếu và làm mới mỗi 4 giờ trong hạn mức.

8 thg 9, 2026 · 9 phút đọc

=GOLDPRICE() trả giá XAU/USD trực tiếp mỗi ounce troy từ goldprice.dev vào ô bất kỳ sau khi thêm Apps Script. Giá Free trễ 15 phút. IMPORTDATA không đọc được JSON này, nên cần hàm tùy chỉnh. GOLDHISTORY() trả giá đóng cửa hằng ngày trong 30 ngày; GOLDPRICEDELTA() trả chênh lệch giá và tỷ lệ có dấu so với ô tham chiếu đã chuẩn hóa. Không lưu khóa API. Chạy installGoldRefresh() một lần để gắn ô làm mới và đặt lịch bốn giờ trong hạn mức.

Bắt đầu mà không lưu khóa API trong bảng tính

Endpoint giá giao ngay XAU và lịch sử hằng ngày trong 30 ngày đều dùng được khi không đăng nhập. Hướng dẫn này không lưu thông tin xác thực trong Apps Script. Truy cập ẩn danh theo hạn mức Free: 10 yêu cầu/phút, 100/giờ và 5.000/tháng mỗi IP. Làm mới mỗi bốn giờ nằm trong hạn mức.

Google có thể dùng chung IP, khiến hạn mức ẩn danh thay đổi. Bảng tính dùng chung hoặc vận hành cần hạn mức riêng: gọi proxy máy chủ, giữ khóa goldprice.dev tại đó. Đừng lưu bearer token trong script mà người chỉnh sửa đọc được. Khóa Free cũng có 5.000 lượt/tháng, tính theo tài khoản thay vì IP chung.

Nhận hạn mức yêu cầu riêng

Các bước dưới đây không cần khóa. Khóa API miễn phí cấp hạn mức riêng cho bạn. Lịch sử dài hơn và hạn mức cao hơn cần gói trả phí.

Thêm ba hàm giá vàng vào Apps Script

Mở bảng tính, chọn Tiện ích mở rộng → Apps Script. Xóa function myFunction() {}, dán mã dưới đây. GOLDPRICE(metal, currency, refreshToken) lấy giá giao ngay; GOLDHISTORY(fromDate, toDate, refreshToken) lấy giá đóng cửa để vẽ biểu đồ; GOLDPRICEDELTA(referencePrice, metal, currency, refreshToken) trả chênh lệch giá và tỷ lệ có dấu. Các hàm dùng CacheService lưu đệm yêu cầu trùng, không gửi header Authorization, trả chuỗi lỗi thay vì ném ngoại lệ.

Để theo dõi số lượng, chi phí mua và định giá tham khảo trong cùng bảng tính, dùng mẫu theo dõi danh mục vàng.

Code.gs
// google-sheets: Apps Script Code.gsconst SPOT_CACHE_TTL = 55; // Cache successful spot responses for 55 seconds.const BARS_CACHE_TTL = 300; // Cache successful daily bars for 5 minutes.const REFRESH_RANGE_NAME = 'GOLDPRICE_REFRESH_TOKEN';const SPREADSHEET_ID_KEY = 'GOLDPRICE_SPREADSHEET_ID';const INSTALLER_EMAIL_KEY = 'GOLDPRICE_INSTALLER_EMAIL';const LAST_REFRESH_AT_KEY = 'GOLDPRICE_LAST_REFRESH_AT';const MIN_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;/** * Returns the current spot price for a metal/currency pair. * @customfunction */function GOLDPRICE(metal, currency, refreshToken) {  const symbol = metal + '-' + currency + '-SPOT';  const cache = CacheService.getScriptCache();  const cacheKey = 'spot_' + symbol;  const cached = cache.get(cacheKey);  if (cached) return cached;  const url = `https://api.goldprice.dev/v1/spot/${symbol}`;  const resp = UrlFetchApp.fetch(url, {    muteHttpExceptions: true,  });  const code = resp.getResponseCode();  if (code === 401) return '#ACCESS_ERROR: use a server-side proxy';  if (code === 403) return '#PLAN_GATED: symbol not on your tier';  if (code === 429) return '#RATE_LIMIT: wait a minute and retry';  if (code !== 200) return '#ERROR_' + code;  const data = JSON.parse(resp.getContentText());  if (!data || !data.price) return '#NO_DATA';  // Price stays a string, same as the API wire format: Sheets displays and  // sorts it fine. Wrap in VALUE() if you need it in arithmetic.  cache.put(cacheKey, data.price, SPOT_CACHE_TTL);  return data.price;}/** * Compares live spot with a reference cell in the same currency and unit. * Spills [signed price delta, signed percentage delta] into two cells. * @customfunction */function GOLDPRICEDELTA(referencePrice, metal, currency, refreshToken) {  const reference = Number(referencePrice);  if (!Number.isFinite(reference) || reference <= 0) {    return [['#BAD_REFERENCE: enter a positive price', '']];  }  const liveResult = GOLDPRICE(metal, currency, refreshToken);  const live = Number(liveResult);  if (!Number.isFinite(live)) return [[liveResult, '']];  const signedDelta = live - reference;  return [[Number(signedDelta.toFixed(2)), signedDelta / reference]];}/** * Returns chart-ready daily closes for a date range. * @customfunction */function GOLDHISTORY(fromDate, toDate, refreshToken) {  const now = new Date();  const rollingFrom = new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000);  const from = toIsoDate_(    fromDate === '' || fromDate == null ? rollingFrom : fromDate  );  const to = toIsoDate_(toDate === '' || toDate == null ? now : toDate);  if (!from || !to) return [['#BAD_DATE: put real dates in B2/B3']];  const cache = CacheService.getScriptCache();  const cacheKey = 'bars_' + from + '_' + to;  const cached = cache.get(cacheKey);  if (cached) return JSON.parse(cached);  const url = 'https://api.goldprice.dev/v1/bars?symbol=XAU-USD-SPOT&interval=1d'    + '&from=' + from + '&to=' + to;  const resp = UrlFetchApp.fetch(url, {    muteHttpExceptions: true,  });  const code = resp.getResponseCode();  if (code === 401) return [['#ACCESS_ERROR: use a server-side proxy']];  if (code === 403) return [['#PLAN_GATED: range not on your tier']];  if (code === 429) return [['#RATE_LIMIT: wait a minute and retry']];  if (code !== 200) return [['#ERROR_' + code]];  const data = JSON.parse(resp.getContentText());  const bars = Array.isArray(data.bars) ? data.bars : [];  if (bars.length === 0) return [['#NO_DATA: empty range']];  // /v1/bars returns bars newest-first; reverse to chronological order so  // a line chart built from this range reads left-to-right in time.  const rows = bars.slice().reverse().map(function (bar) {    return [bar.bar_start.slice(0, 10), Number(bar.close)];  });  cache.put(cacheKey, JSON.stringify(rows), BARS_CACHE_TTL);  return rows;}function installGoldRefresh() {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();    if (!spreadsheet) throw new Error('Open the bound Google Sheet and run again');    const installerEmail = Session.getEffectiveUser().getEmail();    if (!installerEmail) throw new Error('Run the installer from your Google account');    const properties = PropertiesService.getScriptProperties();    const existingInstaller = properties.getProperty(INSTALLER_EMAIL_KEY);    if (existingInstaller && existingInstaller !== installerEmail) {      throw new Error('Gold refresh is already owned by another editor');    }    const refreshRange = spreadsheet.getActiveSheet().getRange('B1');    spreadsheet.setNamedRange(REFRESH_RANGE_NAME, refreshRange);    // getProjectTriggers() only returns this user's triggers. The shared    // installer-email claim above prevents a second editor from creating a    // second schedule, while this replacement keeps owner reruns idempotent.    ScriptApp.getProjectTriggers()      .filter(function (trigger) {        return trigger.getHandlerFunction() === 'refreshGoldData';      })      .forEach(function (trigger) {        ScriptApp.deleteTrigger(trigger);      });    ScriptApp.newTrigger('refreshGoldData')      .timeBased()      .everyHours(4)      .create();    properties.setProperties({      [SPREADSHEET_ID_KEY]: spreadsheet.getId(),      [INSTALLER_EMAIL_KEY]: installerEmail,    });  } finally {    lock.releaseLock();  }  refreshGoldData();}function uninstallGoldRefresh() {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    const properties = PropertiesService.getScriptProperties();    const installerEmail = Session.getEffectiveUser().getEmail();    const existingInstaller = properties.getProperty(INSTALLER_EMAIL_KEY);    if (existingInstaller && existingInstaller !== installerEmail) {      throw new Error('Only the trigger owner can uninstall gold refresh');    }    ScriptApp.getProjectTriggers()      .filter(function (trigger) {        return trigger.getHandlerFunction() === 'refreshGoldData';      })      .forEach(function (trigger) {        ScriptApp.deleteTrigger(trigger);      });    properties.deleteProperty(SPREADSHEET_ID_KEY);    properties.deleteProperty(INSTALLER_EMAIL_KEY);    properties.deleteProperty(LAST_REFRESH_AT_KEY);  } finally {    lock.releaseLock();  }}function refreshGoldData(event) {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    // Clock triggers pass an event object, but they cannot supply the metal,    // currency, date-range, or reference arguments required by sheet formulas.    // This wrapper only changes the named token cell those formulas reference.    const properties = PropertiesService.getScriptProperties();    const spreadsheetId = properties.getProperty(SPREADSHEET_ID_KEY);    if (!spreadsheetId) throw new Error('Run installGoldRefresh first');    // Script properties and the script lock are shared across editors. This    // gate coalesces legacy triggers owned by different users into one refresh    // per four hours even though Apps Script cannot enumerate their triggers.    const now = Date.now();    const lastRefreshAt = Number(properties.getProperty(LAST_REFRESH_AT_KEY));    if (lastRefreshAt && now - lastRefreshAt < MIN_REFRESH_INTERVAL_MS) return;    const spreadsheet = SpreadsheetApp.openById(spreadsheetId);    const refreshRange = spreadsheet.getRangeByName(REFRESH_RANGE_NAME);    if (!refreshRange) throw new Error('Run installGoldRefresh to restore B1');    refreshRange.setValue(new Date(now).toISOString());    properties.setProperty(LAST_REFRESH_AT_KEY, String(now));  } finally {    lock.releaseLock();  }}function toIsoDate_(value) {  const d = value instanceof Date ? value : new Date(value);  if (isNaN(d.getTime())) return null;  return d.toISOString().slice(0, 10);}

Thiết lập các ô giá, lịch sử và tham chiếu

Đặt giá trị kích hoạt làm mới ở B1, giá tham chiếu USD mỗi ounce troy ở C1 nếu cần. Để trống B2 và B3 để lấy 30 ngày gần nhất, hoặc nhập ngày cố định. GOLDPRICEDELTA() trả chênh lệch giá trực tiếp trừ tham chiếu và tỷ lệ có dấu. Hai ô phải cùng tiền tệ, đơn vị.

Tài liệu nêu báo giá GOOGLEFINANCE có thể trễ tới 20 phút; không hỗ trợ mọi hợp đồng tương lai, Apps Script không đọc được lịch sử. Chuẩn hóa giá tham chiếu về USD/ounce troy tại C1; goldprice.dev cung cấp giá và lịch sử qua API. Không truyền NOW(), TODAY() vào hàm: GOLDHISTORY() tự tính ngày khi B2/B3 trống.

Công thức Sheets · Liên kết B1/B2/B3/C1
// B1: mã làm mới do refreshGoldData() ghi// B2/B3: để trống để lấy 30 ngày lịch gần nhất, hoặc nhập ngày cố định// C1: optional reference quote normalized to USD per troy ounce// Giá giao ngay bằng USD hiện tại: tính lại khi B1 thay đổi=GOLDPRICE("XAU", "USD", $B$1)// Giá giao ngay trừ C1: trả về [chênh lệch USD có dấu, chênh lệch phần trăm có dấu]// Định dạng ô kết quả thứ hai thành phần trăm.=GOLDPRICEDELTA($C$1, "XAU", "USD", $B$1)// Giá giao ngay bằng EUR hiện tại: cả hai dùng chung ô mã làm mới=GOLDPRICE("XAU", "EUR", $B$1)// Giá đóng cửa 30 ngày, sẵn để vẽ biểu đồ (ngày, giá đóng cửa)=GOLDHISTORY($B$2, $B$3, $B$1)

Tạo biểu đồ đường từ dữ liệu lịch sử

Chọn các hàng do GOLDHISTORY() trả về, chọn Chèn → Biểu đồ, rồi chọn Biểu đồ đường. Số hàng tùy khoảng ngày và dữ liệu hằng ngày hiện có. Hàm đã đảo dữ liệu API từ mới nhất trước sang thứ tự thời gian, nên trục ngang chạy từ cũ đến mới mà không cần sắp xếp thêm.

Đặt lịch làm mới mỗi 4 giờ

Chọn một tài khoản Google quản lý lịch. Ai từng theo hướng dẫn cũ phải xóa mục refreshGoldData của mình trong Apps Script → Trình kích hoạt. Chạy installGoldRefresh một lần, cấp quyền. Hàm gắn B1, tạo lịch bốn giờ. Khóa chung và dấu thời gian gộp các lịch cũ không truy cập được thành một lần làm mới.

Khi bàn giao, chủ lịch chạy uninstallGoldRefresh, rồi chủ mới cài lại. Nếu chủ cũ vắng mặt, xóa ba mục GOLDPRICE_* trong Cài đặt dự án → Thuộc tính script rồi cài lại; cơ chế chung vẫn ngăn gọi trùng. Bốn công thức × sáu lần làm mới: tối đa 24 lượt/ngày, khoảng 720/tháng, dưới mức Free 100 yêu cầu/giờ, 5.000/tháng/IP.

Kết quả mong đợi

JSON
{  "symbol": "XAU",  "quote_currency": "USD",  "unit": "troy_ounce",  "contract_type": "spot",  "price": "4012.75",  "is_stale": false,  "computed_at": "2026-10-23T07:00:18Z"}

GOLDPRICE hiển thị trường price ở trên dưới dạng văn bản, ví dụ "4012.75". GOLDPRICEDELTA trả chênh lệch giá và tỷ lệ thập phân có dấu vào hai ô; định dạng ô thứ hai thành Phần trăm. GOLDHISTORY điền các hàng [date, close] hiện có trong khoảng ngày yêu cầu.

Lỗi thường gặp

MãBiểu hiệnCách sửa
401Ô hiện #ACCESS_ERROR: use a server-side proxyYêu cầu ẩn danh bị từ chối. Kiểm tra Apps Script → Lượt thực thi trước. Nếu bảng tính cần hạn mức có xác thực, gửi yêu cầu qua proxy phía máy chủ; đừng dán khóa vào script gắn với bảng tính.
403Ô hiện #PLAN_GATED: symbol not on your tierFree hỗ trợ giá giao ngay vàng (XAU), bạc (XAG) và đồng (HG). Kiểm tra mã; không hỗ trợ bạch kim và palladium. Xem /pricing.
429Ô hiện #RATE_LIMIT: wait a minute and retryIP dùng chung chạm hạn mức Free: 10 yêu cầu/phút, 100/giờ hoặc 5.000/tháng. Bộ nhớ đệm thường ngăn lỗi này; tăng các hằng số TTL, giảm công thức khác nhau hoặc chuyển yêu cầu có xác thực qua proxy phía máy chủ.
N/AGOLDHISTORY hiện #BAD_DATE: put real dates in B2/B3B2 hoặc B3 chứa văn bản mà Apps Script không đọc được thành ngày, như nhãn thay vì giá trị. Nhập ngày hợp lệ, hoặc tham chiếu ô có định dạng Ngày qua Định dạng → Số → Ngày.
N/AGOLDPRICEDELTA hiện #BAD_REFERENCE: enter a positive priceC1 đang trống, chứa văn bản, bằng không hoặc âm. Nhập giá tham chiếu dương, cùng tiền tệ và đơn vị mỗi ounce troy với công thức giá giao ngay.
N/AÔ cứ hiện Loading…Apps Script cần quyền gửi yêu cầu ra ngoài. Chọn tên hàm trên thanh công cụ của trình chỉnh sửa, chạy thủ công một lần để hiện yêu cầu cấp quyền, rồi chấp nhận.

Làm thêm

  • Tạo công thức cảnh báo giá: =IF(GOLDPRICE("XAU","USD",$B$1) > 4800, "ALERT", "OK")
  • Để lấy lịch sử hằng ngày xa hơn, dùng proxy phía máy chủ có khóa: Basic hỗ trợ 1 năm; Plus và Pro hỗ trợ toàn bộ lịch sử
  • Theo dõi bạc cùng vàng trên Free, dùng chung ô kích hoạt làm mới
  • Dùng các hàng GOLDHISTORY để kiểm thử chiến lược trên dữ liệu quá khứ, xem kiểm thử chiến lược vàng với dữ liệu OHLC
  • Tham chiếu =GOLDPRICE(...) trong mô hình định giá hoặc chi phí để cập nhật từ giá trực tiếp thay cho số nhập cố định

Câu hỏi

Giá cập nhật bao lâu một lần?

Endpoint giao ngay cập nhật mỗi 60 giây. Giá Free hoặc không dùng khóa trễ 15 phút; gói trả phí nhận giá trực tiếp. Bộ nhớ đệm trong GOLDPRICE theo nhịp này, nên các lần gọi trong TTL trả cùng giá trị mà không tốn thêm lượt gọi API.

Làm mới mỗi 4 giờ có nằm trong hạn mức Free không?

Có. Bốn công thức dùng ô $B$1: hai GOLDPRICE, một GOLDPRICEDELTA, một GOLDHISTORY. Lịch 4 giờ chạy 6 lần/ngày × 4 lượt = 24 lượt/ngày, khoảng 720/tháng. GOLDPRICEDELTA thường dùng lại giá USD đã lưu đệm. Mức này dưới giới hạn ẩn danh mỗi IP: 10 yêu cầu/phút, 100/giờ và 5.000/tháng.

Vì sao không dùng GOOGLEFINANCE để lấy giá vàng?

GOOGLEFINANCE hỗ trợ chứng khoán, tiền tệ, không có API giao ngay XAU/USD. Báo giá có thể trễ tới 20 phút; không hỗ trợ mọi kỳ hạn, Apps Script không đọc được lịch sử. Dùng GOLDPRICE(), GOLDHISTORY(), GOLDPRICEDELTA() lấy giá, lịch sử, so sánh tham chiếu chuẩn hóa; không mặc định hai công cụ tương đương.

Vì sao lịch gọi refreshGoldData thay vì GOLDPRICE?

Apps Script truyền đối tượng sự kiện đồng hồ cho hàm xử lý, nhưng không truyền được kim loại, tiền tệ, khoảng ngày hay giá tham chiếu mà công thức cần. installGoldRefresh() đặt lịch cho refreshGoldData(event): hàm chỉ ghi dấu thời gian mới vào vùng B1 đã đặt tên, khiến cả bốn công thức tính lại.

Tôi có thể chia sẻ bảng tính cho người khác chỉnh sửa không?

Có. Ví dụ này truy cập ẩn danh, không lưu khóa API, nên người chỉnh sửa có thể xem hoặc sao chép script mà không nhận thông tin xác thực. Nếu sau này cần hạn mức có xác thực, giữ bearer token trong proxy phía máy chủ và để bảng tính gọi dịch vụ đó.

Tôi có thể dùng cho mục đích thương mại không?

Mọi gói đều cho phép sử dụng thương mại. Gói trả phí không cần liên kết ghi công; Free yêu cầu liên kết hiển thị rõ tới goldprice.dev gần phần giá.

Hỗ trợ những kim loại nào?

Giá giao ngay vàng (XAU), bạc (XAG) và đồng (HG) có trên mọi gói, kể cả Free. Không gói nào hỗ trợ bạch kim và palladium. Xem chi tiết trong tài liệu API.

Gói Free có thực sự miễn phí không?

Có. Giá XAU và lịch sử hằng ngày 30 ngày không cần khóa: 10 yêu cầu/phút, 100/giờ, 5.000/tháng mỗi IP. Khóa tài khoản miễn phí có cùng hạn mức tháng, không cần thẻ, không hết hạn, phù hợp khi cần hạn mức theo tài khoản cho tích hợp phía máy chủ.

Trợ lý AI như Claude có thể viết giúp tôi không?

Có. Cho Claude, Cursor hoặc ChatGPT truy cập MCP server goldprice.dev hoặc llms.txt, rồi yêu cầu: Viết hàm Google Sheets dùng api.goldprice.dev, kèm hàm cài đặt chạy lại không tạo trùng, gắn ô làm mới có tên và tạo lịch bốn giờ. Nhờ đọc schema hiện hành, mã dùng đúng tên trường, mã lỗi ngay lần đầu.

Apps Script có dùng được với Excel không?

Không. Để lấy giá vàng trong Excel, xem hướng dẫn Excel. Excel dùng Power Query M để kết nối dữ liệu, không dùng Apps Script.

Đọc tiếp

Bắt đầu với khóa API miễn phí

5.000 yêu cầu mỗi tháng trên gói miễn phí. Không cần thẻ tín dụng.

Lấy khóa miễn phí