reformat
This commit is contained in:
parent
c6c3240cbf
commit
c0a3b9b805
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
148
src/index.ts
148
src/index.ts
|
@ -1,38 +1,50 @@
|
||||||
import { Account, Connection, PublicKey } from "@solana/web3.js"
|
import { Account, Connection, PublicKey } from '@solana/web3.js'
|
||||||
import { Market } from "@project-serum/serum"
|
import { Market } from '@project-serum/serum'
|
||||||
import cors from "cors"
|
import cors from 'cors'
|
||||||
import express from "express"
|
import express from 'express'
|
||||||
import { Tedis, TedisPool } from "tedis"
|
import { Tedis, TedisPool } from 'tedis'
|
||||||
import { URL } from "url"
|
import { URL } from 'url'
|
||||||
import { decodeRecentEvents } from "./events"
|
import { decodeRecentEvents } from './events'
|
||||||
import { MarketConfig, Trade, TradeSide } from "./interfaces"
|
import { MarketConfig, Trade, TradeSide } from './interfaces'
|
||||||
import { RedisConfig, RedisStore, createRedisStore } from "./redis"
|
import { RedisConfig, RedisStore, createRedisStore } from './redis'
|
||||||
import { resolutions, sleep } from "./time"
|
import { resolutions, sleep } from './time'
|
||||||
|
|
||||||
|
|
||||||
async function collectEventQueue(m: MarketConfig, r: RedisConfig) {
|
async function collectEventQueue(m: MarketConfig, r: RedisConfig) {
|
||||||
const store = await createRedisStore(r, m.marketName)
|
const store = await createRedisStore(r, m.marketName)
|
||||||
const marketAddress = new PublicKey(m.marketPk)
|
const marketAddress = new PublicKey(m.marketPk)
|
||||||
const programKey = new PublicKey(m.programId)
|
const programKey = new PublicKey(m.programId)
|
||||||
const connection = new Connection(m.clusterUrl)
|
const connection = new Connection(m.clusterUrl)
|
||||||
const market = await Market.load(connection, marketAddress, undefined, programKey)
|
const market = await Market.load(
|
||||||
|
connection,
|
||||||
|
marketAddress,
|
||||||
|
undefined,
|
||||||
|
programKey
|
||||||
|
)
|
||||||
|
|
||||||
async function fetchTrades(lastSeqNum?: number): Promise<[Trade[], number]> {
|
async function fetchTrades(lastSeqNum?: number): Promise<[Trade[], number]> {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const accountInfo = await connection.getAccountInfo(market["_decoded"].eventQueue)
|
const accountInfo = await connection.getAccountInfo(
|
||||||
|
market['_decoded'].eventQueue
|
||||||
|
)
|
||||||
if (accountInfo === null) {
|
if (accountInfo === null) {
|
||||||
throw new Error(`Event queue account for market ${m.marketName} not found`)
|
throw new Error(
|
||||||
|
`Event queue account for market ${m.marketName} not found`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const { header, events } = decodeRecentEvents(accountInfo.data, lastSeqNum);
|
const { header, events } = decodeRecentEvents(accountInfo.data, lastSeqNum)
|
||||||
const takerFills = events.filter((e) => e.eventFlags.fill && !e.eventFlags.maker)
|
const takerFills = events.filter(
|
||||||
const trades = takerFills.map(e => market.parseFillEvent(e)).map((e) => {
|
(e) => e.eventFlags.fill && !e.eventFlags.maker
|
||||||
return {
|
)
|
||||||
price: e.price,
|
const trades = takerFills
|
||||||
side: e.side === "buy" ? TradeSide.Buy : TradeSide.Sell,
|
.map((e) => market.parseFillEvent(e))
|
||||||
size: e.size,
|
.map((e) => {
|
||||||
ts: now,
|
return {
|
||||||
}
|
price: e.price,
|
||||||
})
|
side: e.side === 'buy' ? TradeSide.Buy : TradeSide.Sell,
|
||||||
|
size: e.size,
|
||||||
|
ts: now,
|
||||||
|
}
|
||||||
|
})
|
||||||
/*
|
/*
|
||||||
if (trades.length > 0)
|
if (trades.length > 0)
|
||||||
console.log({e: events.map(e => e.eventFlags), takerFills, trades})
|
console.log({e: events.map(e => e.eventFlags), takerFills, trades})
|
||||||
|
@ -51,42 +63,45 @@ async function collectEventQueue(m: MarketConfig, r: RedisConfig) {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const lastSeqNum = (await store.loadNumber('LASTSEQ'));
|
const lastSeqNum = await store.loadNumber('LASTSEQ')
|
||||||
const [trades, currentSeqNum] = await fetchTrades(lastSeqNum);
|
const [trades, currentSeqNum] = await fetchTrades(lastSeqNum)
|
||||||
storeTrades(trades)
|
storeTrades(trades)
|
||||||
store.storeNumber('LASTSEQ', currentSeqNum)
|
store.storeNumber('LASTSEQ', currentSeqNum)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err.toString().split("\n", 1)[0]
|
const error = err.toString().split('\n', 1)[0]
|
||||||
console.error(m.marketName, { error })
|
console.error(m.marketName, { error })
|
||||||
}
|
}
|
||||||
await sleep({Seconds: process.env.INTERVAL ? parseInt(process.env.INTERVAL) : 10})
|
await sleep({
|
||||||
|
Seconds: process.env.INTERVAL ? parseInt(process.env.INTERVAL) : 10,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const redisUrl = new URL(process.env.REDISCLOUD_URL || "redis://localhost:6379")
|
const redisUrl = new URL(process.env.REDISCLOUD_URL || 'redis://localhost:6379')
|
||||||
const host = redisUrl.hostname
|
const host = redisUrl.hostname
|
||||||
const port = parseInt(redisUrl.port)
|
const port = parseInt(redisUrl.port)
|
||||||
let password: string | undefined
|
let password: string | undefined
|
||||||
if (redisUrl.password !== "") {
|
if (redisUrl.password !== '') {
|
||||||
password = redisUrl.password
|
password = redisUrl.password
|
||||||
}
|
}
|
||||||
|
|
||||||
const network = "mainnet-beta"
|
const network = 'mainnet-beta'
|
||||||
const clusterUrl = process.env.RPC_ENDPOINT_URL || "https://solana-api.projectserum.com"
|
const clusterUrl =
|
||||||
const programIdV3 = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
|
process.env.RPC_ENDPOINT_URL || 'https://solana-api.projectserum.com'
|
||||||
|
const programIdV3 = '9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'
|
||||||
|
|
||||||
const nativeMarketsV3: Record<string, string> = {
|
const nativeMarketsV3: Record<string, string> = {
|
||||||
"BTC/USDT": "C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4",
|
'BTC/USDT': 'C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4',
|
||||||
"ETH/USDT": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
|
'ETH/USDT': '7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF',
|
||||||
"SOL/USDT": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1",
|
'SOL/USDT': 'HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1',
|
||||||
"SRM/USDT": "AtNnsY1AyRERWJ8xCskfz38YdvruWVJQUVXgScC1iPb",
|
'SRM/USDT': 'AtNnsY1AyRERWJ8xCskfz38YdvruWVJQUVXgScC1iPb',
|
||||||
"RAY/USDT": "teE55QrL4a4QSfydR9dnHF97jgCfptpuigbb53Lo95g",
|
'RAY/USDT': 'teE55QrL4a4QSfydR9dnHF97jgCfptpuigbb53Lo95g',
|
||||||
"BTC/USDC": "A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw",
|
'BTC/USDC': 'A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw',
|
||||||
"ETH/USDC": "4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX",
|
'ETH/USDC': '4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX',
|
||||||
"SOL/USDC": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT",
|
'SOL/USDC': '9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT',
|
||||||
"SRM/USDC": "ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA",
|
'SRM/USDC': 'ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA',
|
||||||
"RAY/USDC": "2xiv8A5xrJ7RnGdxXB42uFEkYHJjszEhaJyKKt4WaLep",
|
'RAY/USDC': '2xiv8A5xrJ7RnGdxXB42uFEkYHJjszEhaJyKKt4WaLep',
|
||||||
"MCAPS/USDC": "GgzXqy6agt7nnfoPjAEAFpWqnUwLBK5r2acaAQqXiEM8",
|
'MCAPS/USDC': 'GgzXqy6agt7nnfoPjAEAFpWqnUwLBK5r2acaAQqXiEM8',
|
||||||
}
|
}
|
||||||
|
|
||||||
const symbolsByPk = Object.assign(
|
const symbolsByPk = Object.assign(
|
||||||
|
@ -97,21 +112,26 @@ const symbolsByPk = Object.assign(
|
||||||
function collectMarketData(programId: string, markets: Record<string, string>) {
|
function collectMarketData(programId: string, markets: Record<string, string>) {
|
||||||
Object.entries(markets).forEach((e) => {
|
Object.entries(markets).forEach((e) => {
|
||||||
const [marketName, marketPk] = e
|
const [marketName, marketPk] = e
|
||||||
const marketConfig = { clusterUrl, programId, marketName, marketPk } as MarketConfig
|
const marketConfig = {
|
||||||
collectEventQueue(marketConfig, { host, port, password, db: 0});
|
clusterUrl,
|
||||||
|
programId,
|
||||||
|
marketName,
|
||||||
|
marketPk,
|
||||||
|
} as MarketConfig
|
||||||
|
collectEventQueue(marketConfig, { host, port, password, db: 0 })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
collectMarketData(programIdV3, nativeMarketsV3)
|
collectMarketData(programIdV3, nativeMarketsV3)
|
||||||
|
|
||||||
const max_conn = parseInt(process.env.REDIS_MAX_CONN || "") || 200;
|
const max_conn = parseInt(process.env.REDIS_MAX_CONN || '') || 200
|
||||||
const redisConfig = { host, port, password, db: 0, max_conn }
|
const redisConfig = { host, port, password, db: 0, max_conn }
|
||||||
const pool = new TedisPool(redisConfig)
|
const pool = new TedisPool(redisConfig)
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
|
|
||||||
app.get("/tv/config", async (req, res) => {
|
app.get('/tv/config', async (req, res) => {
|
||||||
const response = {
|
const response = {
|
||||||
supported_resolutions: Object.keys(resolutions),
|
supported_resolutions: Object.keys(resolutions),
|
||||||
supports_group_request: false,
|
supports_group_request: false,
|
||||||
|
@ -122,17 +142,17 @@ app.get("/tv/config", async (req, res) => {
|
||||||
res.send(response)
|
res.send(response)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get("/tv/symbols", async (req, res) => {
|
app.get('/tv/symbols', async (req, res) => {
|
||||||
const symbol = req.query.symbol as string
|
const symbol = req.query.symbol as string
|
||||||
const response = {
|
const response = {
|
||||||
name: symbol,
|
name: symbol,
|
||||||
ticker: symbol,
|
ticker: symbol,
|
||||||
description: symbol,
|
description: symbol,
|
||||||
type: "Spot",
|
type: 'Spot',
|
||||||
session: "24x7",
|
session: '24x7',
|
||||||
exchange: "Mango",
|
exchange: 'Mango',
|
||||||
listed_exchange: "Mango",
|
listed_exchange: 'Mango',
|
||||||
timezone: "Etc/UTC",
|
timezone: 'Etc/UTC',
|
||||||
has_intraday: true,
|
has_intraday: true,
|
||||||
supported_resolutions: Object.keys(resolutions),
|
supported_resolutions: Object.keys(resolutions),
|
||||||
minmov: 1,
|
minmov: 1,
|
||||||
|
@ -141,7 +161,7 @@ app.get("/tv/symbols", async (req, res) => {
|
||||||
res.send(response)
|
res.send(response)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get("/tv/history", async (req, res) => {
|
app.get('/tv/history', async (req, res) => {
|
||||||
// parse
|
// parse
|
||||||
const marketName = req.query.symbol as string
|
const marketName = req.query.symbol as string
|
||||||
const marketPk = nativeMarketsV3[marketName]
|
const marketPk = nativeMarketsV3[marketName]
|
||||||
|
@ -154,7 +174,7 @@ app.get("/tv/history", async (req, res) => {
|
||||||
const validResolution = resolution != undefined
|
const validResolution = resolution != undefined
|
||||||
const validFrom = true || new Date(from).getFullYear() >= 2021
|
const validFrom = true || new Date(from).getFullYear() >= 2021
|
||||||
if (!(validSymbol && validResolution && validFrom)) {
|
if (!(validSymbol && validResolution && validFrom)) {
|
||||||
const error = { s: "error", validSymbol, validResolution, validFrom }
|
const error = { s: 'error', validSymbol, validResolution, validFrom }
|
||||||
console.error({ marketName, error })
|
console.error({ marketName, error })
|
||||||
res.status(404).send(error)
|
res.status(404).send(error)
|
||||||
return
|
return
|
||||||
|
@ -176,7 +196,7 @@ app.get("/tv/history", async (req, res) => {
|
||||||
}
|
}
|
||||||
const candles = await store.loadCandles(resolution, from, to)
|
const candles = await store.loadCandles(resolution, from, to)
|
||||||
const response = {
|
const response = {
|
||||||
s: "ok",
|
s: 'ok',
|
||||||
t: candles.map((c) => c.start / 1000),
|
t: candles.map((c) => c.start / 1000),
|
||||||
c: candles.map((c) => c.close),
|
c: candles.map((c) => c.close),
|
||||||
o: candles.map((c) => c.open),
|
o: candles.map((c) => c.open),
|
||||||
|
@ -191,12 +211,12 @@ app.get("/tv/history", async (req, res) => {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error({ req, e })
|
console.error({ req, e })
|
||||||
const error = { s: "error" }
|
const error = { s: 'error' }
|
||||||
res.status(500).send(error)
|
res.status(500).send(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get("/trades/address/:marketPk", async (req, res) => {
|
app.get('/trades/address/:marketPk', async (req, res) => {
|
||||||
// parse
|
// parse
|
||||||
const marketPk = req.params.marketPk as string
|
const marketPk = req.params.marketPk as string
|
||||||
const marketName = symbolsByPk[marketPk]
|
const marketName = symbolsByPk[marketPk]
|
||||||
|
@ -204,7 +224,7 @@ app.get("/trades/address/:marketPk", async (req, res) => {
|
||||||
// validate
|
// validate
|
||||||
const validPk = marketName != undefined
|
const validPk = marketName != undefined
|
||||||
if (!validPk) {
|
if (!validPk) {
|
||||||
const error = { s: "error", validPk }
|
const error = { s: 'error', validPk }
|
||||||
console.error({ marketPk, error })
|
console.error({ marketPk, error })
|
||||||
res.status(404).send(error)
|
res.status(404).send(error)
|
||||||
return
|
return
|
||||||
|
@ -224,9 +244,9 @@ app.get("/trades/address/:marketPk", async (req, res) => {
|
||||||
marketAddress: marketPk,
|
marketAddress: marketPk,
|
||||||
price: t.price,
|
price: t.price,
|
||||||
size: t.size,
|
size: t.size,
|
||||||
side: t.side == TradeSide.Buy ? "buy" : "sell",
|
side: t.side == TradeSide.Buy ? 'buy' : 'sell',
|
||||||
time: t.ts,
|
time: t.ts,
|
||||||
orderId: "",
|
orderId: '',
|
||||||
feeCost: 0,
|
feeCost: 0,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -238,10 +258,10 @@ app.get("/trades/address/:marketPk", async (req, res) => {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error({ req, e })
|
console.error({ req, e })
|
||||||
const error = { s: "error" }
|
const error = { s: 'error' }
|
||||||
res.status(500).send(error)
|
res.status(500).send(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpPort = parseInt(process.env.PORT || "5000")
|
const httpPort = parseInt(process.env.PORT || '5000')
|
||||||
app.listen(httpPort)
|
app.listen(httpPort)
|
||||||
|
|
Loading…
Reference in New Issue