mirror of
https://github.com/luxfi/uma.git
synced 2026-11-03 10:55:24 +00:00
203 lines
6.8 KiB
JavaScript
203 lines
6.8 KiB
JavaScript
const { web3 } = require("hardhat");
|
|
const { assert } = require("chai");
|
|
const { QuandlPriceFeed } = require("../../dist/price-feed/QuandlPriceFeed");
|
|
const { NetworkerMock } = require("../../dist/price-feed/NetworkerMock");
|
|
const { spyLogIncludes, SpyTransport } = require("../../dist/logger/SpyTransport");
|
|
const winston = require("winston");
|
|
const moment = require("moment");
|
|
const { parseFixed } = require("@uma/common");
|
|
const sinon = require("sinon");
|
|
|
|
describe("QuandlPriceFeed.js", function () {
|
|
let quandlPriceFeed;
|
|
// Keep test timezone consistent with price feed's. Set mock time equal to
|
|
// close of the most recent day.
|
|
let mockTime = moment("2021-03-12", "YYYY-MM-DD").endOf("day").unix();
|
|
let networker;
|
|
let spy;
|
|
|
|
const apiKey = "123";
|
|
const datasetCode = "CHRIS";
|
|
const databaseCode = "CME_MGC1";
|
|
const lookback = 0; // Lookback doesn't matter in this test since we populate the Networker
|
|
const minTimeBetweenUpdates = 43200; // 12 hours.
|
|
// with fake data.
|
|
const getTime = () => mockTime;
|
|
|
|
const { toBN } = web3.utils;
|
|
const pricePrecision = 6;
|
|
|
|
const convertPriceFeedDecimals = (number) => {
|
|
return toBN(parseFixed(number.toString().substring(0, pricePrecision), pricePrecision).toString());
|
|
};
|
|
// Fake data to inject. Stress test with prices with lots of decimals
|
|
// to ensure pricefeed correctly handles the edge case where API price # decimals > priceFeedDecimals
|
|
const validResponses = [
|
|
{
|
|
dataset_data: {
|
|
column_names: [
|
|
"Date",
|
|
"Open",
|
|
"High",
|
|
"Low",
|
|
"Last",
|
|
"Change",
|
|
"Settle",
|
|
"Volume",
|
|
"Previous Day Open Interest",
|
|
],
|
|
start_date: "2021-03-09",
|
|
end_date: "2021-03-12",
|
|
frequency: "daily",
|
|
data: [
|
|
["2021-03-12", 1720.12222222222, 1727.9, 1696.6, 1725.2222222222, -2.8, 1719.8, 64823.0, 21102.0],
|
|
["2021-03-11", 1725.02222222222, 1738.0, 1716.7, 1720.9222222222, 0.8, 1722.6, 51362.0, 20996.0],
|
|
["2021-03-10", 1714.62222222222, 1725.4, 1705.8, 1725.2222222222, 4.9, 1721.8, 45206.0, 20960.0],
|
|
["2021-03-09", 1679.52222222222, 1718.8, 1676.8, 1714.1222222222, 38.9, 1716.9, 56794.0, 22221.0],
|
|
],
|
|
collapse: "daily",
|
|
},
|
|
},
|
|
];
|
|
|
|
beforeEach(async function () {
|
|
spy = sinon.spy();
|
|
networker = new NetworkerMock();
|
|
quandlPriceFeed = new QuandlPriceFeed(
|
|
winston.createLogger({ level: "info", transports: [new SpyTransport({ level: "debug" }, { spy: spy })] }),
|
|
web3,
|
|
apiKey,
|
|
datasetCode,
|
|
databaseCode,
|
|
lookback,
|
|
networker,
|
|
getTime,
|
|
pricePrecision // Add arbitrary decimal conversion.
|
|
);
|
|
});
|
|
|
|
it("No update", async function () {
|
|
assert.equal(quandlPriceFeed.getCurrentPrice(), undefined);
|
|
assert.isTrue(await quandlPriceFeed.getHistoricalPrice(1000).catch(() => true));
|
|
assert.equal(quandlPriceFeed.getLastUpdateTime(), undefined);
|
|
assert.equal(quandlPriceFeed.getLookback(), lookback);
|
|
});
|
|
|
|
it("Basic historical price", async function () {
|
|
// Inject data.
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
await quandlPriceFeed.update();
|
|
|
|
// Historical prices will be available from the oldest price's open time to the newest price's
|
|
// close time.
|
|
const validLookback =
|
|
quandlPriceFeed.historicalPricePeriods[quandlPriceFeed.historicalPricePeriods.length - 1].closeTime -
|
|
quandlPriceFeed.historicalPricePeriods[0].openTime;
|
|
|
|
// Before period 1 should fail.
|
|
assert.isTrue(await quandlPriceFeed.getHistoricalPrice(mockTime - validLookback - 1).catch(() => true));
|
|
|
|
// Prices should always be the open price, unless the timestamp is beyond the last period,
|
|
// in which it uses the most recent close price.
|
|
|
|
// During period 1.
|
|
assert.equal(
|
|
(await quandlPriceFeed.getHistoricalPrice(mockTime - validLookback + 1)).toString(),
|
|
convertPriceFeedDecimals("1679.522222")
|
|
);
|
|
|
|
// During period 2.
|
|
assert.equal(
|
|
(await quandlPriceFeed.getHistoricalPrice(mockTime - validLookback + 24 * 3600 + 1)).toString(),
|
|
convertPriceFeedDecimals("1714.622222")
|
|
);
|
|
|
|
// During period 3.
|
|
assert.equal(
|
|
(await quandlPriceFeed.getHistoricalPrice(mockTime - validLookback + 2 * 24 * 3600 + 1)).toString(),
|
|
convertPriceFeedDecimals("1725.022222")
|
|
);
|
|
|
|
// After period 3 should return the most recent (close) price.
|
|
assert.equal(
|
|
(await quandlPriceFeed.getHistoricalPrice(mockTime + 1)).toString(),
|
|
convertPriceFeedDecimals("1725.222222")
|
|
);
|
|
});
|
|
|
|
it("Basic current price", async function () {
|
|
// Inject data.
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
await quandlPriceFeed.update();
|
|
|
|
// Should return the most recent, close price in the data.
|
|
assert.equal(quandlPriceFeed.getCurrentPrice().toString(), convertPriceFeedDecimals("1725.222222"));
|
|
});
|
|
|
|
it("Last update time", async function () {
|
|
// Inject data.
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
await quandlPriceFeed.update();
|
|
|
|
// Should return the mock time.
|
|
assert.equal(quandlPriceFeed.getLastUpdateTime(), mockTime);
|
|
});
|
|
|
|
it("No or bad response", async function () {
|
|
// Bad price response.
|
|
networker.getJsonReturns = [
|
|
{
|
|
dataset_data: {
|
|
data: [
|
|
// Some missing daily data
|
|
[],
|
|
["2021-03-12", 1720.12222222222, 1727.9, 1696.6, 1725.2222222222, -2.8, 1719.8, 64823.0, 21102.0],
|
|
],
|
|
},
|
|
},
|
|
{
|
|
dataset_data: {
|
|
// Empty data array
|
|
data: [],
|
|
},
|
|
},
|
|
{
|
|
dataset_data: {
|
|
// Missing data array
|
|
},
|
|
},
|
|
{ error: "test" },
|
|
];
|
|
|
|
// Update should throw errors in all cases.
|
|
assert.isTrue(await quandlPriceFeed.update().catch(() => true), "Update didn't throw");
|
|
assert.isTrue(await quandlPriceFeed.update().catch(() => true), "Update didn't throw");
|
|
assert.isTrue(await quandlPriceFeed.update().catch(() => true), "Update didn't throw");
|
|
assert.isTrue(await quandlPriceFeed.update().catch(() => true), "Update didn't throw");
|
|
|
|
assert.equal(quandlPriceFeed.getCurrentPrice(), undefined);
|
|
assert.isTrue(await quandlPriceFeed.getHistoricalPrice(mockTime).catch(() => true));
|
|
});
|
|
|
|
it("Update frequency", async function () {
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
await quandlPriceFeed.update();
|
|
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
// Advancing the pricefeed's time forward by anything less than `minTimeBetweenUpdates`
|
|
// should make it skip an update.
|
|
const originalMockTime = mockTime;
|
|
mockTime += minTimeBetweenUpdates - 1;
|
|
|
|
await quandlPriceFeed.update();
|
|
|
|
assert.isTrue(spyLogIncludes(spy, -1, "Update skipped because the last one was too recent"));
|
|
assert.equal(quandlPriceFeed.getLastUpdateTime(), originalMockTime);
|
|
});
|
|
});
|