init commit

This commit is contained in:
Tyler Shipe 2021-07-26 13:58:58 -04:00
commit 0b646d8187
17 changed files with 3598 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.env
node_modules
dist

37
config/config.js Normal file
View File

@ -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,
},
},
},
};

43
lib/fetchStats.ts Normal file
View File

@ -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

View File

@ -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;');
},
};

View File

@ -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")
},
}

View File

@ -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")
},
}

View File

@ -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) => {},
}

View File

@ -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) => {},
}

21
models/index.js Normal file
View File

@ -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

View File

@ -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

View File

@ -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

40
package.json Normal file
View File

@ -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"
}
}

57
src/index.ts Normal file
View File

@ -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}`))

3
src/schedule.ts Normal file
View File

@ -0,0 +1,3 @@
import fetchAndPersistStats from '../lib/fetchStats';
fetchAndPersistStats();

15
tsconfig.json Normal file
View File

@ -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"],
}

3173
yarn.lock Normal file

File diff suppressed because it is too large Load Diff