init commit
This commit is contained in:
commit
0b646d8187
|
@ -0,0 +1,3 @@
|
||||||
|
.env
|
||||||
|
node_modules
|
||||||
|
dist
|
|
@ -0,0 +1,37 @@
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
development: {
|
||||||
|
use_env_variable: 'TIMESCALEDB_URL',
|
||||||
|
dialect: 'postgres',
|
||||||
|
protocol: 'postgres',
|
||||||
|
dialectOptions: {
|
||||||
|
ssl: {
|
||||||
|
require: true,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
use_env_variable: 'TIMESCALEDB_URL',
|
||||||
|
dialect: 'postgres',
|
||||||
|
protocol: 'postgres',
|
||||||
|
dialectOptions: {
|
||||||
|
ssl: {
|
||||||
|
require: true,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
use_env_variable: 'TIMESCALEDB_URL',
|
||||||
|
dialect: 'postgres',
|
||||||
|
protocol: 'postgres',
|
||||||
|
dialectOptions: {
|
||||||
|
ssl: {
|
||||||
|
require: true,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Connection, PublicKey } from "@solana/web3.js"
|
||||||
|
import { IDS, MangoClient } from "@blockworks-foundation/mango-client"
|
||||||
|
import PerpMarketStats from "../models/perp_market_stats"
|
||||||
|
|
||||||
|
async function fetchAndPersistStats() {
|
||||||
|
// const clusterUrls = IDS.cluster_urls[cluster]
|
||||||
|
// if (!clusterUrls) return
|
||||||
|
// const client = new MangoClient()
|
||||||
|
// const connection = new Connection(IDS.cluster_urls[cluster], "singleGossip")
|
||||||
|
// const stats: any[][] = await Promise.all(
|
||||||
|
// MANGO_GROUPS.map(async (mangoGroupName) => {
|
||||||
|
// const assets = IDS[cluster].mango_groups?.[mangoGroupName]?.symbols
|
||||||
|
// const mangoGroupId = IDS[cluster].mango_groups?.[mangoGroupName]?.mango_group_pk
|
||||||
|
// const mangoGroupPk = new PublicKey(mangoGroupId)
|
||||||
|
// const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
|
||||||
|
// const mangoGroupStats = Object.keys(assets).map((symbol, index) => {
|
||||||
|
// const totalDeposits = mangoGroup.getUiTotalDeposit(index)
|
||||||
|
// const totalBorrows = mangoGroup.getUiTotalBorrow(index)
|
||||||
|
// return {
|
||||||
|
// time: new Date(),
|
||||||
|
// symbol,
|
||||||
|
// totalDeposits,
|
||||||
|
// totalBorrows,
|
||||||
|
// depositInterest: mangoGroup.getDepositRate(index),
|
||||||
|
// borrowInterest: mangoGroup.getBorrowRate(index),
|
||||||
|
// utilization: totalDeposits > 0.0 ? totalBorrows / totalDeposits : 0.0,
|
||||||
|
// mangoGroup: mangoGroupName,
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// return mangoGroupStats
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// const tableName = cluster === "devnet" ? DevnetStats : MainnetStats
|
||||||
|
// try {
|
||||||
|
// console.log("stats", stats.flat())
|
||||||
|
// await tableName.bulkCreate(stats.flat())
|
||||||
|
// console.log("stats inserted")
|
||||||
|
// } catch (err) {
|
||||||
|
// console.log("failed to insert stats", err)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fetchAndPersistStats
|
|
@ -0,0 +1,11 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;');
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.sequelize.query('DROP EXTENSION timescaledb;');
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,44 @@
|
||||||
|
"use strict"
|
||||||
|
module.exports = {
|
||||||
|
up: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.createTable(
|
||||||
|
"spot_market_stats",
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
publicKey: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
mangoGroup: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
depositRate: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
borrowRate: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
totalDeposits: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
totalBorrows: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
baseOraclePrice: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
type: Sequelize.DATE,
|
||||||
|
},
|
||||||
|
utilization: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ timestamps: false }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
down: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.dropTable("spot_market_stats")
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
"use strict"
|
||||||
|
module.exports = {
|
||||||
|
up: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.createTable(
|
||||||
|
"perp_market_stats",
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
publicKey: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
mangoGroup: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
longFunding: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
shortFunding: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
openInterest: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
baseOraclePrice: {
|
||||||
|
type: Sequelize.DECIMAL,
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
type: Sequelize.DATE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ timestamps: false }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
down: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.dropTable("perp_market_stats")
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
"use strict"
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.sequelize.query("SELECT create_hypertable('spot_market_stats', 'time');")
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface, Sequelize) => {},
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
"use strict"
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.sequelize.query("SELECT create_hypertable('perp_market_stats', 'time');")
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface, Sequelize) => {},
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
"use strict"
|
||||||
|
const Sequelize = require("sequelize")
|
||||||
|
const env = process.env.NODE_ENV || "development"
|
||||||
|
const config = require(__dirname + "/../config/config.js")[env]
|
||||||
|
const db = {}
|
||||||
|
|
||||||
|
const sequelize = new Sequelize(process.env[config.use_env_variable], config)
|
||||||
|
|
||||||
|
sequelize
|
||||||
|
.authenticate()
|
||||||
|
.then(function (err) {
|
||||||
|
console.log("Connection has been established successfully.")
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log("Unable to connect to the database:", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
db.sequelize = sequelize
|
||||||
|
db.Sequelize = Sequelize
|
||||||
|
|
||||||
|
module.exports = db
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Sequelize, DataTypes } from "sequelize"
|
||||||
|
import db from "./index"
|
||||||
|
|
||||||
|
const PerpMarketStats = db.sequelize.define(
|
||||||
|
"perp_market_stats",
|
||||||
|
{
|
||||||
|
name: { type: DataTypes.STRING, allowNull: false },
|
||||||
|
longFunding: {
|
||||||
|
type: DataTypes.DECIMAL,
|
||||||
|
get() {
|
||||||
|
const value = this.getDataValue("longFunding")
|
||||||
|
return value ? parseFloat(value) : null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shortFunding: {
|
||||||
|
type: DataTypes.DECIMAL,
|
||||||
|
get() {
|
||||||
|
const value = this.getDataValue("shortFunding")
|
||||||
|
return value ? parseFloat(value) : null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openInterest: {
|
||||||
|
type: DataTypes.DECIMAL,
|
||||||
|
get() {
|
||||||
|
const value = this.getDataValue("openInterest")
|
||||||
|
return value === null ? null : parseFloat(value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
baseOraclePrice: {
|
||||||
|
type: DataTypes.DECIMAL,
|
||||||
|
get() {
|
||||||
|
const value = this.getDataValue("baseOraclePrice")
|
||||||
|
return value === null ? null : parseFloat(value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mangoGroup: DataTypes.STRING,
|
||||||
|
publicKey: DataTypes.STRING,
|
||||||
|
time: DataTypes.DATE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
PerpMarketStats.removeAttribute("id")
|
||||||
|
|
||||||
|
export default PerpMarketStats
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Sequelize, DataTypes } from "sequelize"
|
||||||
|
import db from "./index"
|
||||||
|
|
||||||
|
const PerpMarketStats = db.sequelize.define(
|
||||||
|
"perp_market_stats",
|
||||||
|
{
|
||||||
|
symbol: { type: DataTypes.STRING, allowNull: false },
|
||||||
|
totalDeposits: {
|
||||||
|
type: DataTypes.DECIMAL,
|
||||||
|
get() {
|
||||||
|
const value = this.getDataValue("totalDeposits")
|
||||||
|
return value ? parseFloat(value) : null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
totalBorrows: {
|
||||||
|
type: DataTypes.DECIMAL,
|
||||||
|
get() {
|
||||||
|
const value = this.getDataValue("totalBorrows")
|
||||||
|
return value ? parseFloat(value) : null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
depositRate: {
|
||||||
|
type: DataTypes.DECIMAL,
|
||||||
|
get() {
|
||||||
|
const value = this.getDataValue("depositRate")
|
||||||
|
return value === null ? null : parseFloat(value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borrowRate: {
|
||||||
|
type: DataTypes.DECIMAL,
|
||||||
|
get() {
|
||||||
|
const value = this.getDataValue("borrowRate")
|
||||||
|
return value === null ? null : parseFloat(value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mangoGroup: DataTypes.STRING,
|
||||||
|
publicKey: DataTypes.STRING,
|
||||||
|
utilization: DataTypes.DECIMAL,
|
||||||
|
time: DataTypes.DATE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
PerpMarketStats.removeAttribute("id")
|
||||||
|
|
||||||
|
export default PerpMarketStats
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "mango-stats-v3",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --resolveJsonModule",
|
||||||
|
"dev": "ts-node-dev src/index.ts",
|
||||||
|
"clean": "rm -rf dist",
|
||||||
|
"start": "node dist/src/index.js",
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"format": "eslint src/**/*.ts --fix",
|
||||||
|
"db-setup": "sequelize db:drop && sequelize db:create tsdb && sequelize db:migrate",
|
||||||
|
"db-migrate": "sequelize db:migrate"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.17.11",
|
||||||
|
"@types/node": "^14.14.28",
|
||||||
|
"@types/validator": "^13.1.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.15.1",
|
||||||
|
"@typescript-eslint/parser": "^4.15.1",
|
||||||
|
"eslint": "^7.20.0",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
|
"ts-node-dev": "^1.1.1",
|
||||||
|
"typescript": "^4.1.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@blockworks-foundation/mango-client": "^3.0.1",
|
||||||
|
"@solana/web3.js": "^1.18.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"node-cron": "^2.0.3",
|
||||||
|
"pg": "^8.5.1",
|
||||||
|
"pg-hstore": "^2.3.3",
|
||||||
|
"sequelize": "^6.5.0",
|
||||||
|
"sequelize-cli": "^6.2.0",
|
||||||
|
"ts-node": "^9.1.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import dotenv from "dotenv"
|
||||||
|
dotenv.config()
|
||||||
|
import express from "express"
|
||||||
|
import cors from "cors"
|
||||||
|
import { Op, QueryTypes } from "sequelize"
|
||||||
|
import PerpMarketStats from "../models/perp_market_stats"
|
||||||
|
import { sequelize } from "../models"
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
app.use(express.json(), cors())
|
||||||
|
|
||||||
|
app.get("/", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const mangoGroup = (req.query.mangoGroup as string) || ""
|
||||||
|
let stats
|
||||||
|
if (mangoGroup === "BTC_ETH_USDT" || mangoGroup === "BTC_ETH_SOL_SRM_USDC") {
|
||||||
|
stats = await sequelize.query(
|
||||||
|
`SELECT time_bucket('60 minutes', time) AS "hourly",
|
||||||
|
"symbol",
|
||||||
|
avg("totalDeposits")::float AS "totalDeposits",
|
||||||
|
avg("totalBorrows")::float AS "totalBorrows",
|
||||||
|
avg("utilization")::float AS "utilization",
|
||||||
|
avg("depositInterest")::float AS "depositInterest",
|
||||||
|
avg("borrowInterest")::float AS "borrowInterest",
|
||||||
|
min("time") AS "time"
|
||||||
|
FROM mainnet_stats
|
||||||
|
WHERE time > current_date - interval '90' day AND "mangoGroup" = :mangoGroup
|
||||||
|
GROUP BY "hourly", "symbol"
|
||||||
|
ORDER BY "hourly" ASC;`,
|
||||||
|
{
|
||||||
|
replacements: { mangoGroup },
|
||||||
|
type: QueryTypes.SELECT,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
stats = await PerpMarketStats.findAll({
|
||||||
|
order: [["time", "ASC"]],
|
||||||
|
where: { mangoGroup: { [Op.or]: [null, mangoGroup] } },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
res.send(stats)
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error inserting data", e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get("/current", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const stats = await PerpMarketStats.findAll({ limit: 3, order: [["time", "DESC"]] })
|
||||||
|
res.send(stats)
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error inserting data", e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(process.env.PORT, () => console.log(`Server listening at http://localhost:${process.env.PORT}`))
|
|
@ -0,0 +1,3 @@
|
||||||
|
import fetchAndPersistStats from '../lib/fetchStats';
|
||||||
|
|
||||||
|
fetchAndPersistStats();
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions":
|
||||||
|
{
|
||||||
|
"allowJs": true,
|
||||||
|
"target": "es2019",
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "dist",
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
},
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"include": ["src/**/*.ts", "lib/**/*.ts", "config"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
}
|
Loading…
Reference in New Issue