Skip to content

Commit

Permalink
api: new endpoint for fund perf (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
yurushao authored Apr 3, 2024
1 parent 1c6df44 commit a81f054
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
58 changes: 58 additions & 0 deletions api/prices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Fetch price data from benchmarks.pyth.network
* Rate limit: https://docs.pyth.network/benchmarks/rate-limits
*/
const priceHistory = async (symbol) => {
if (!["Crypto.ETH/USD", "Crypto.BTC/USD"].includes(symbol)) {
console.error("Invalid symbol", symbol);
return [];
}
const tsEnd = Math.floor(Date.now() / 1000);
const tsStart = tsEnd - 60 * 60 * 24 * 30; // 30 days ago

const queryParams = {
symbol,
resolution: "1D",
from: tsStart,
to: tsEnd
};
const baseUrl =
"https://benchmarks.pyth.network/v1/shims/tradingview/history";
const queryString = new URLSearchParams(queryParams).toString();
const urlWithQuery = `${baseUrl}?${queryString}`;
const response = await fetch(urlWithQuery, {
method: "GET",
headers: { Accept: "application/json" }
});
if (response.status === 200) {
// {"s":"ok","t":[...],"o":[...],"h":[...],"l":[...],"c":[...],"v":[...]}
// https://www.tradingview.com/rest-api-spec/#operation/getHistory
const { t: timestamps, c: closingPrices } = await response.json();
return { timestamps, closingPrices };
}
return [];
};

/**
* Percent change in the last X days
*/
const fundPerformance = (btcWeight, btcPrices, ethWeight, ethPrices) => {
if (btcPrices.length === 0 || ethPrices.length === 0) {
throw new Error("No price data");
}
if (btcPrices.length !== ethPrices.length) {
throw new Error("Price data mismatch");
}

const btcChanges = btcPrices.map((p) => p / btcPrices[0] - 1);
const ethChanges = ethPrices.map((p) => p / ethPrices[0] - 1);

const weightedChanges = btcChanges.map((btcChange, i) => {
const ethChange = ethChanges[i];
return btcWeight * btcChange + ethWeight * ethChange;
});

return weightedChanges;
};

module.exports = { priceHistory, fundPerformance };
32 changes: 30 additions & 2 deletions api/server.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,47 @@
const express = require("express");
const { createCanvas, loadImage } = require("canvas");
const { validatePubkey } = require("./validation");
const { priceHistory, fundPerformance } = require("./prices");

BASE_URL = "https://api.glam.systems";

const app = express();
app.use(express.static("public"));

app.get("/", (req, res) => {
res.send("Hello from App Engine!");
res.send(`Hello from ${BASE_URL}!`);
});

app.get("/_/health", (req, res) => {
res.send("ok");
});

app.get("/fund/:pubkey/perf", async (req, res) => {
// TODO: validate input
// TODO: Should we fetch weights from blockchain, or let client side pass them in?
// Client side should have all fund info including assets and weights
const [btcWeight, ethWeight] = [0.7, 0.3];
console.log(`ethWeight: ${ethWeight}, btcWeight: ${btcWeight}`);
const { timestamps, closingPrices: ethClosingPrices } = await priceHistory(
"Crypto.ETH/USD"
);
const { closingPrices: btcClosingPrices } = await priceHistory(
"Crypto.BTC/USD"
);

// console.log(
// `Last30dPrices (USD): ETH ${ethClosingPrices}, BTC ${btcClosingPrices}`
// );

const performance = fundPerformance(
btcWeight,
btcClosingPrices,
ethWeight,
ethClosingPrices
);
res.send(JSON.stringify({ timestamps, performance }));
});

app.get("/metadata/:pubkey", async (req, res) => {
const key = validatePubkey(req.params.pubkey);
if (!key) {
Expand Down Expand Up @@ -90,6 +117,7 @@ app.get("/image/:pubkey.png", async (req, res) => {

// Listen to the App Engine-specified port, or 8080 otherwise
const PORT = process.env.PORT || 8080;
const host = process.env.NODE_ENV ? "" : "http://localhost";
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}...`);
console.log(`Server listening on port ${host}:${PORT}...`);
});

0 comments on commit a81f054

Please sign in to comment.