Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rss updates #428

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions api/backtest/historical.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-disable import/no-anonymous-default-export */
/* eslint-disable no-loop-func */

import { biggerblockno, point, resultSet } from '../../src/utils/rssUtils';
import { uniswap, testUni } from './uniswap';
import { sushiswap, testSushi } from './sushiswap';

import { fetchLatestBlock } from '../rss';

import fetch from "node-fetch";

const INF = 1000000000000000;

export default async (address: string, financials: {liquidationIncentive: number, slippage: number}) => {

const currentBlock = await fetch('https://api.blockcypher.com/v1/eth/main')
.then((res) => res.json())
.then((data) => data.height);

return await new Promise(async (resolve) => {

const config = {
period: 68, // around 15 mins of blocktime
no_segments: 2900, // keep divisible of 100 for batching
end: await fetchLatestBlock()
}

// // it is possible to return either sushiswap or uniswap data in the form of point[]; currently uniswap is much faster
let data = await uniswap(address, config);

if (data) {
let result = await calcTokenDown(data as point[], financials);

resolve(result);
} else {
resolve(false);
}

})
}

const calcTokenDown = async (data: point[], financials: {liquidationIncentive: number, slippage: number}) => {

let result = await work(data, financials);

let TOKEN0DOWN = result;

return {
TOKEN0DOWN
} as resultSet
}

const work = async (data: any[], financials: {liquidationIncentive: number, slippage: number}) => {

const shocks = await simulateshock(data.sort(biggerblockno), financials);
// console.log("shocks: " + JSON.stringify(result.shocks));

return shocks;
}

const simulateshock = async (data: point[], financials: {liquidationIncentive: number, slippage: number}) => {

let shocks = new Promise( (resolve) => {
let shocks: number = 0;

data.forEach( async (point, index) => {
const i: number = index;

for (let j = 0; i + j + 1 < data.length; ++j) { // check whether liquidation feasible at i+j+1
if ((data[i + j].token0Price - data[i + j + 1].token0Price) / point.token0Price < financials.liquidationIncentive - financials.slippage) { // liquidation feasible
let token0down = (data[i].token0Price - data[i + j + 1].token0Price) / point.token0Price;
if (token0down > shocks) {
shocks = token0down;
}
break;
}
}
})
resolve(shocks);
})

return await shocks;
}
110 changes: 110 additions & 0 deletions api/backtest/sushiswap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable import/no-anonymous-default-export */
/* eslint-disable no-loop-func */

import { ChainId, Token, Pair } from '@sushiswap/sdk'
import { parse, point } from '../../src/utils/rssUtils';
import checksum from 'eth-checksum';
import sushiData from '@sushiswap/sushi-data';

const sushiswap = async (address: string, config: {period: number, no_segments: number, end: number}) => {

const id = fetchPairID(address);

let blocks: number[][] = await blocknumbers(config);

let results: point[] = await queryManySushi(blocks, id);

return results;
}

const queryManySushi = async (blocks: number[][], id: string) => {

let set: any[] = [];

for (let i = 0; i < blocks.length; i++) {
let chunk = await querySushi(blocks[i], id);
set.push(chunk);
}

return flatten(set) as point[];
}

const querySushi = async (blocks: number[], id: string) => {
try {
const data = await sushiData.timeseries({blocks: blocks, target: sushiData.exchange.pair}, {pair_address: id});
return await parse(data);
} catch (e) {
console.log(e)
return [];
}
}

const blocknumbers = async (config: {period: number, no_segments: number, end: number}) => {

const period = config.period; // 68 blocks of 13.2 seconds is 15 min
const no_segments = config.no_segments;
const blockend = config.end;

let numbers: number[] = await new Promise(async (resolve) => {
let array: number[] = [];
for (let blockno = blockend - period * no_segments; blockno < blockend; blockno += period) {
array.push(blockno);
}
resolve(array);
});

// split block number array from 500 into sets of 100
const chunk = 100;
const result = numbers.reduce((resultArray: any[], item, index) => {
const chunkIndex = Math.floor(index / chunk)

if(!resultArray[chunkIndex]) {
resultArray[chunkIndex] = []
}

resultArray[chunkIndex].push(item)

return resultArray
}, [])

return result;
}

const fetchPairID = (address: string) => {

const token0 = new Token(ChainId.MAINNET, checksum.encode(address), 18);

const weth = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const token1 = new Token(ChainId.MAINNET, checksum.encode(weth), 18);

const pair = Pair.getAddress(token0, token1);

return pair.toLowerCase();

}

const testSushi = async (address: string) => {
const config = {
period: 68, // around 15 mins of blocktime
no_segments: 500, // keep divisible of 100 for batching
end: 12635203
}
const id = fetchPairID(address)

try {
const blocks = await blocknumbers(config)
const data = await sushiData.timeseries({blocks: blocks[0], target: sushiData.exchange.pair}, {pair_address: id});
return data
} catch (e) {
console.log(e);
return false;
}
}

function flatten(arr: any[]) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}

export { sushiswap, testSushi }
173 changes: 173 additions & 0 deletions api/backtest/uniswap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/* eslint-disable import/no-anonymous-default-export */
/* eslint-disable no-loop-func */
import fetch from 'node-fetch';

import { ChainId, Token, Pair } from '@uniswap/sdk'
import { point } from '../../src/utils/rssUtils';

import checksum from 'eth-checksum';

import {queue} from '../rss';

const createQuery = async (id: string, config) => {

const blockend = config.end;
const period = config.period;
const no_segments = config.no_segments;

const uniswapMax = 100;

const segmentNumber = no_segments / uniswapMax;

let sets: string[] = await new Promise ( async (resolve) => {
let segments: string[] = [];

for (let i = 0; i < segmentNumber; i++) {

let query: string = ``;

let x: number = (segmentNumber - i);

let startingBlock = period * x * uniswapMax;
let endingBlock = period * (x - 1) * uniswapMax;

for (let blockno = blockend - startingBlock; blockno < blockend - endingBlock; blockno += period) {

let str = `
set_${blockno}: pair(
id: "${id}"
block: {number: ${blockno}}
){
token0Price
token1Price
reserve0
reserve1
}
`;

query = query + str;
}

segments.push(query);
}
resolve(segments as string[])
});

return sets.map( (set) => {
return `{ ${set} }`;
});
}

const queryUniswap = async (sets: string[]) => {

const points = await Promise.all(sets.map( async (set) => {

let bufferObj = Buffer.from(set, "utf8");
const key = bufferObj.toString('base64');

// request balancer
const data = async () => {
return await fetch(
"https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2",
{
method: "post",
body: JSON.stringify({ query: set }),
headers: { "Content-Type": "application/json" },
timeout: 50000,
}
).then((res) => res.json());
}

let points = await queue.request((retry) => data()
.then(response => response.data)
.catch(error => {
if (error.response.status === 429) {
return retry(error.response.data.parameters.retry_after)
}
throw error;
}), key, 'uniswap')
.then(response => response)
.catch(error => console.error(error));

let final = await addBlocknumber(points);

return final as any;

}))

// flatten array
let p: point[] = [].concat.apply([], points);
return p;
};

const addBlocknumber = async (data: Object) => {
const keys = Object.keys(data);
const values = Object.values(data)

const points: point[] = values.map((value, index) => {
let blocknumber = keys[index].slice(4);
value.blocknumber = parseFloat(blocknumber);
value.token0Price = parseFloat(value.token0Price);
value.token1Price = parseFloat(value.token1Price);
value.reserve0 = parseFloat(value.reserve0);
value.reserve1 = parseFloat(value.reserve1);

return value;
})

return points;
}


const fetchPairID = (address: string) => {
const token0 = new Token(ChainId.MAINNET, checksum.encode(address), 18);

const weth = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const token1 = new Token(ChainId.MAINNET, checksum.encode(weth), 18);

const pair = Pair.getAddress(token0, token1);

return pair;
}


const uniswap = async (address: string, config: {period: number, no_segments: number, end: number}) => {

const id = fetchPairID(address).toLowerCase();

const sets: string[] = await createQuery(id, config);

const results: point[] = await queryUniswap(sets);

return results
}

const testUni = async (address: string) => {

const id = fetchPairID(address).toLowerCase();

const query = `{
pair(id: "${id}") {
id
}
}
`
let data = await fetch(
"https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2",
{
method: "post",
body: JSON.stringify({ query: query }),
headers: { "Content-Type": "application/json" },
timeout: 50000,
}
).then((res) => res.json());

if (data.data.pair.id) {
return true;
} else {
return false;
}

}

export {uniswap, testUni};
Loading