add Invert price feed

This commit is contained in:
Trooper
2022-06-27 20:18:01 +02:00
parent 3aa573c14c
commit 903e139700
2 changed files with 246 additions and 227 deletions

View File

@@ -193,6 +193,20 @@ With constant mode, you can set a fixed price to market make. The bot will not c
}
```
###### Invert price feed
For some pairs, you might just find a price feed for the inverse of the pair. If you want to mm for ZZ-USDC and only find a USDC-ZZ price feed. In those cases, you need to invert the fee. This will only work if the secondary price feed is inverted as well or set to null.
Example:
```
"ETH-USDC": {
"side": "d",
"priceFeedPrimary": "uniswapv3:0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
"priceFeedSecondary": null,
"invert": true,
....
}
```
## Pair Options
These pair options can be set for each pair individual. You can even use more then on option per pair (though they might cancel each other out).

View File

@@ -32,7 +32,7 @@ else {
MM_CONFIG = JSON.parse(mmConfigFile);
}
if (MM_CONFIG.feeToken) {
FEE_TOKEN = MM_CONFIG.feeToken;
FEE_TOKEN = MM_CONFIG.feeToken;
}
let activePairs = [];
for (let marketId in MM_CONFIG.pairs) {
@@ -48,7 +48,7 @@ const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId);
const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "rinkeby";
let ethersProvider;
const providerUrl = (process.env.INFURA_URL || MM_CONFIG.infuraUrl);
if(providerUrl && ETH_NETWORK=="mainnet") {
if (providerUrl && ETH_NETWORK == "mainnet") {
ethersProvider = ethers.getDefaultProvider(providerUrl);
} else {
ethersProvider = ethers.getDefaultProvider(ETH_NETWORK);
@@ -62,7 +62,7 @@ try {
syncProvider = await zksync.getDefaultProvider(ETH_NETWORK);
const keys = [];
const ethPrivKey = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey);
if(ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); }
if (ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); }
let ethPrivKeys;
if (process.env.ETH_PRIVKEYS) {
ethPrivKeys = JSON.parse(process.env.ETH_PRIVKEYS);
@@ -70,14 +70,14 @@ try {
else {
ethPrivKeys = MM_CONFIG.ethPrivKeys;
}
if(ethPrivKeys && ethPrivKeys.length > 0) {
ethPrivKeys.forEach( key => {
if(key != "" && !keys.includes(key)) {
if (ethPrivKeys && ethPrivKeys.length > 0) {
ethPrivKeys.forEach(key => {
if (key != "" && !keys.includes(key)) {
keys.push(key);
}
});
}
for(let i=0; i<keys.length; i++) {
for (let i = 0; i < keys.length; i++) {
let ethWallet = new ethers.Wallet(keys[i]);
let syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider);
if (!(await syncWallet.isSigningKeySet())) {
@@ -117,13 +117,13 @@ function onWsOpen() {
indicateLiquidityInterval = setInterval(indicateLiquidity, 5000);
for (let market in MM_CONFIG.pairs) {
if (MM_CONFIG.pairs[market].active) {
const msg = {op:"subscribemarket", args:[CHAIN_ID, market]};
const msg = { op: "subscribemarket", args: [CHAIN_ID, market] };
zigzagws.send(JSON.stringify(msg));
}
}
}
function onWsClose () {
function onWsClose() {
console.log("Websocket closed. Restarting");
setTimeout(() => {
clearInterval(fillOrdersInterval)
@@ -138,12 +138,12 @@ function onWsClose () {
async function handleMessage(json) {
const msg = JSON.parse(json);
if (!(["lastprice", "liquidity2", "fillstatus", "marketinfo"]).includes(msg.op)) console.log(json.toString());
switch(msg.op) {
switch (msg.op) {
case 'error':
const accountId = msg.args?.[1];
if(msg.args[0] == 'fillrequest' && accountId) {
if (msg.args[0] == 'fillrequest' && accountId) {
WALLETS[accountId]['ORDER_BROADCASTING'] = false;
}
}
break;
case 'orders':
const orders = msg.args[0];
@@ -154,8 +154,8 @@ async function handleMessage(json) {
if (fillable.fillable) {
sendFillRequest(order, fillable.walletId);
} else if ([
"sending order already",
"badprice"
"sending order already",
"badprice"
].includes(fillable.reason)) {
OPEN_ORDERS[orderId] = order;
}
@@ -166,14 +166,14 @@ async function handleMessage(json) {
const orderId = msg.args[1];
const fillOrder = msg.args[3];
const wallet = WALLETS[fillOrder.accountId];
if(!wallet) {
console.error("No wallet with this accountId: "+fillOrder.accountId);
if (!wallet) {
console.error("No wallet with this accountId: " + fillOrder.accountId);
break
} else {
try {
await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet);
} catch (e) {
const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,e.message]]]}
const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'r', null, e.message]]] }
zigzagws.send(JSON.stringify(orderCommitMsg));
console.error(e);
}
@@ -182,8 +182,8 @@ async function handleMessage(json) {
break
case "marketinfo":
const marketInfo = msg.args[0];
const marketId = marketInfo.alias;
if(!marketId) break
const marketId = marketInfo.alias;
if (!marketId) break
let oldBaseFee = "N/A", oldQuoteFee = "N/A";
try {
oldBaseFee = MARKETS[marketId].baseFee;
@@ -196,18 +196,18 @@ async function handleMessage(json) {
const newQuoteFee = MARKETS[marketId].quoteFee;
console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`);
if (FEE_TOKEN) break
if(
marketInfo.baseAsset.enabledForFees &&
!FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)
if (
marketInfo.baseAsset.enabledForFees &&
!FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)
) {
FEE_TOKEN_LIST.push(marketInfo.baseAsset.id);
}
if(
marketInfo.quoteAsset.enabledForFees &&
!FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)
FEE_TOKEN_LIST.push(marketInfo.baseAsset.id);
}
if (
marketInfo.quoteAsset.enabledForFees &&
!FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)
) {
FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id);
}
FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id);
}
break
default:
break
@@ -229,7 +229,7 @@ function isOrderFillable(order) {
const expires = order[7];
const side = order[3];
const price = order[4];
const now = Date.now() / 1000 | 0;
if (now > expires) {
@@ -249,7 +249,7 @@ function isOrderFillable(order) {
let quote;
try {
quote = genQuote(chainId, marketId, side, baseQuantity);
quote = genQuote(chainId, marketId, side, baseQuantity);
} catch (e) {
return { fillable: false, reason: e.message }
}
@@ -263,7 +263,7 @@ function isOrderFillable(order) {
const sellCurrency = (side === 's') ? market.quoteAsset.symbol : market.baseAsset.symbol;
const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals;
const sellQuantity = (side === 's') ? quote.quoteQuantity : baseQuantity;
const neededBalanceBN = sellQuantity * 10**sellDecimals;
const neededBalanceBN = sellQuantity * 10 ** sellDecimals;
let goodWalletIds = [];
Object.keys(WALLETS).forEach(accountId => {
const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency];
@@ -284,37 +284,39 @@ function isOrderFillable(order) {
return { fillable: false, reason: "sending order already" };
}
return { fillable: true, reason: null, walletId: goodWalletIds[0]};
return { fillable: true, reason: null, walletId: goodWalletIds[0] };
}
function genQuote(chainId, marketId, side, baseQuantity) {
const market = MARKETS[marketId];
if (CHAIN_ID !== chainId) throw new Error("badchain");
if (!market) throw new Error("badmarket");
if (!(['b','s']).includes(side)) throw new Error("badside");
if (baseQuantity <= 0) throw new Error("badquantity");
const market = MARKETS[marketId];
if (CHAIN_ID !== chainId) throw new Error("badchain");
if (!market) throw new Error("badmarket");
if (!(['b', 's']).includes(side)) throw new Error("badside");
if (baseQuantity <= 0) throw new Error("badquantity");
validatePriceFeed(marketId);
validatePriceFeed(marketId);
const mmConfig = MM_CONFIG.pairs[marketId];
const mmSide = mmConfig.side || 'd';
if (mmSide !== 'd' && mmSide === side) {
throw new Error("badside");
}
const primaryPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary];
if (!primaryPrice) throw new Error("badprice");
const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate);
let quoteQuantity;
if (side === 'b') {
quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee;
}
else if (side === 's') {
quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD);
}
const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6);
if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee");
if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated.");
return { quotePrice, quoteQuantity };
const mmConfig = MM_CONFIG.pairs[marketId];
const mmSide = mmConfig.side || 'd';
if (mmSide !== 'd' && mmSide === side) {
throw new Error("badside");
}
const primaryPrice = (mmConfig.invert)
? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary])
: PRICE_FEEDS[mmConfig.priceFeedPrimary];
if (!primaryPrice) throw new Error("badprice");
const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate);
let quoteQuantity;
if (side === 'b') {
quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee;
}
else if (side === 's') {
quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD);
}
const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6);
if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee");
if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated.");
return { quotePrice, quoteQuantity };
}
function validatePriceFeed(marketId) {
@@ -344,81 +346,81 @@ function validatePriceFeed(marketId) {
// If the secondary price feed varies from the primary price feed by more than 1%, assume something is broken
const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice;
if (percentDiff > 0.03) {
console.error("Primary and secondary price feeds do not match!");
throw new Error("Circuit breaker triggered");
console.error("Primary and secondary price feeds do not match!");
throw new Error("Circuit breaker triggered");
}
return true;
}
async function sendFillRequest(orderreceipt, accountId) {
const chainId = orderreceipt[0];
const orderId = orderreceipt[1];
const marketId = orderreceipt[2];
const market = MARKETS[marketId];
const baseCurrency = market.baseAssetId;
const quoteCurrency = market.quoteAssetId;
const side = orderreceipt[3];
const baseQuantity = orderreceipt[5];
const quoteQuantity = orderreceipt[6];
const quote = genQuote(chainId, marketId, side, baseQuantity);
let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol;
if (side === "b") {
tokenSell = market.baseAssetId;
tokenBuy = market.quoteAssetId;
const chainId = orderreceipt[0];
const orderId = orderreceipt[1];
const marketId = orderreceipt[2];
const market = MARKETS[marketId];
const baseCurrency = market.baseAssetId;
const quoteCurrency = market.quoteAssetId;
const side = orderreceipt[3];
const baseQuantity = orderreceipt[5];
const quoteQuantity = orderreceipt[6];
const quote = genQuote(chainId, marketId, side, baseQuantity);
let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol;
if (side === "b") {
tokenSell = market.baseAssetId;
tokenBuy = market.quoteAssetId;
sellSymbol = market.baseAsset.symbol;
buySymbol = market.quoteAsset.symbol;
// Add 1 bip to to protect against rounding errors
sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals);
buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals);
} else if (side === "s") {
tokenSell = market.quoteAssetId;
tokenBuy = market.baseAssetId;
sellSymbol = market.baseAsset.symbol;
buySymbol = market.quoteAsset.symbol;
// Add 1 bip to to protect against rounding errors
sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals);
buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals);
} else if (side === "s") {
tokenSell = market.quoteAssetId;
tokenBuy = market.baseAssetId;
sellSymbol = market.quoteAsset.symbol;
buySymbol = market.baseAsset.symbol;
// Add 1 bip to to protect against rounding errors
sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals);
buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals);
}
const sellQuantityParsed = syncProvider.tokenSet.parseToken(
tokenSell,
sellQuantity
);
const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed);
const tokenRatio = {};
tokenRatio[tokenBuy] = buyQuantity;
tokenRatio[tokenSell] = sellQuantity;
const oneMinExpiry = (Date.now() / 1000 | 0) + 60;
const orderDetails = {
tokenSell,
tokenBuy,
amount: sellQuantityPacked,
ratio: zksync.utils.tokenRatio(tokenRatio),
validUntil: oneMinExpiry
}
const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails);
sellSymbol = market.quoteAsset.symbol;
buySymbol = market.baseAsset.symbol;
// Add 1 bip to to protect against rounding errors
sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals);
buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals);
}
const sellQuantityParsed = syncProvider.tokenSet.parseToken(
tokenSell,
sellQuantity
);
const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed);
const tokenRatio = {};
tokenRatio[tokenBuy] = buyQuantity;
tokenRatio[tokenSell] = sellQuantity;
const oneMinExpiry = (Date.now() / 1000 | 0) + 60;
const orderDetails = {
tokenSell,
tokenBuy,
amount: sellQuantityPacked,
ratio: zksync.utils.tokenRatio(tokenRatio),
validUntil: oneMinExpiry
}
const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails);
// Set wallet flag
WALLETS[accountId]['ORDER_BROADCASTING'] = true;
// Set wallet flag
WALLETS[accountId]['ORDER_BROADCASTING'] = true;
// ORDER_BROADCASTING should not take longer as 5 sec
setTimeout(function() {
WALLETS[accountId]['ORDER_BROADCASTING'] = false;
}, 5000);
// ORDER_BROADCASTING should not take longer as 5 sec
setTimeout(function () {
WALLETS[accountId]['ORDER_BROADCASTING'] = false;
}, 5000);
const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] };
zigzagws.send(JSON.stringify(resp));
rememberOrder(chainId,
marketId,
orderId,
quote.quotePrice,
sellSymbol,
sellQuantity,
buySymbol,
buyQuantity
);
const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] };
zigzagws.send(JSON.stringify(resp));
rememberOrder(chainId,
marketId,
orderId,
quote.quotePrice,
sellSymbol,
sellQuantity,
buySymbol,
buyQuantity
);
}
async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) {
@@ -426,21 +428,21 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) {
const nonce = swapOffer.nonce;
const userNonce = NONCES[swapOffer.accountId];
if (nonce <= userNonce) {
const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,"Order failed userNonce check."]]]}
const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'r', null, "Order failed userNonce check."]]] }
zigzagws.send(JSON.stringify(orderCommitMsg));
return;
}
// select token to match user's fee token
let feeToken;
if (FEE_TOKEN) {
feeToken = FEE_TOKEN
feeToken = FEE_TOKEN
} else {
feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell))
? swapOffer.tokenSell
: 'ETH'
feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell))
? swapOffer.tokenSell
: 'ETH'
}
const randInt = (Math.random()*1000).toFixed(0);
const randInt = (Math.random() * 1000).toFixed(0);
console.time('syncswap' + randInt);
const swap = await wallet['syncWallet'].syncSwap({
orders: [swapOffer, fillOrder],
@@ -448,7 +450,7 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) {
nonce: fillOrder.nonce
});
const txHash = swap.txHash.split(":")[1];
const txHashMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'b',txHash]]]}
const txHashMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'b', txHash]]] }
zigzagws.send(JSON.stringify(txHashMsg));
console.timeEnd('syncswap' + randInt);
@@ -465,19 +467,19 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) {
success = false;
}
console.timeEnd('receipt' + randInt);
console.log("Swap broadcast result", {swap, receipt});
console.log("Swap broadcast result", { swap, receipt });
let newStatus, error;
if(success) {
if (success) {
afterFill(chainId, orderId, wallet);
newStatus = 'f';
error = null;
} else {
} else {
newStatus = 'r';
error = swap.error.toString();
}
}
const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]}
const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, newStatus, txHash, error]]] }
zigzagws.send(JSON.stringify(orderCommitMsg));
}
@@ -488,25 +490,25 @@ async function fillOpenOrders() {
if (fillable.fillable) {
sendFillRequest(order, fillable.walletId);
delete OPEN_ORDERS[orderId];
}else if (![
} else if (![
"sending order already",
"badprice"
].includes(fillable.reason)) {
].includes(fillable.reason)) {
delete OPEN_ORDERS[orderId];
}
}
}
async function setupPriceFeeds() {
const cryptowatch = [], chainlink = [], uniswapV3 = [];
const cryptowatch = [], chainlink = [], uniswapV3 = [];
for (let market in MM_CONFIG.pairs) {
const pairConfig = MM_CONFIG.pairs[market];
if(!pairConfig.active) { continue; }
if (!pairConfig.active) { continue; }
// This is needed to make the price feed backwards compatalbe with old constant mode:
// "DYDX-USDC": {
// "mode": "constant",
// "initPrice": 20,
if(pairConfig.mode == "constant") {
if (pairConfig.mode == "constant") {
const initPrice = pairConfig.initPrice;
pairConfig['priceFeedPrimary'] = "constant:" + initPrice.toString();
}
@@ -515,38 +517,38 @@ async function setupPriceFeeds() {
// parse keys to lower case to match later PRICE_FEED keys
if (primaryPriceFeed) {
MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase();
MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase();
}
if (secondaryPriceFeed) {
MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase();
MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase();
}
[primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => {
if(!priceFeed) { return; }
if (!priceFeed) { return; }
const [provider, id] = priceFeed.split(':');
switch(provider.toLowerCase()) {
switch (provider.toLowerCase()) {
case 'cryptowatch':
if(!cryptowatch.includes(id)) { cryptowatch.push(id); }
if (!cryptowatch.includes(id)) { cryptowatch.push(id); }
break;
case 'chainlink':
if(!chainlink.includes(id)) { chainlink.push(id); }
if (!chainlink.includes(id)) { chainlink.push(id); }
break;
case 'uniswapv3':
if(!uniswapV3.includes(id)) { uniswapV3.push(id); }
if (!uniswapV3.includes(id)) { uniswapV3.push(id); }
break;
case 'constant':
PRICE_FEEDS['constant:'+id] = parseFloat(id);
PRICE_FEEDS['constant:' + id] = parseFloat(id);
break;
default:
throw new Error("Price feed provider "+provider+" is not available.")
throw new Error("Price feed provider " + provider + " is not available.")
break;
}
});
}
if(chainlink.length > 0) await chainlinkSetup(chainlink);
if(cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch);
if(uniswapV3.length > 0) await uniswapV3Setup(uniswapV3);
}
});
}
if (chainlink.length > 0) await chainlinkSetup(chainlink);
if (cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch);
if (uniswapV3.length > 0) await uniswapV3Setup(uniswapV3);
console.log(PRICE_FEEDS);
console.log(PRICE_FEEDS);
}
async function cryptowatchWsSetup(cryptowatchMarketIds) {
@@ -561,7 +563,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) {
const exchange = cryptowatchMarket.exchange;
const pair = cryptowatchMarket.pair;
const key = `market:${exchange}:${pair}`;
PRICE_FEEDS['cryptowatch:'+cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key];
PRICE_FEEDS['cryptowatch:' + cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key];
} catch (e) {
console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId);
}
@@ -592,7 +594,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) {
function onopen() {
cryptowatch_ws.send(JSON.stringify(subscriptionMsg));
}
function onmessage (data) {
function onmessage(data) {
const msg = JSON.parse(data);
if (!msg.marketUpdate) return;
@@ -602,7 +604,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) {
let price = ask / 2 + bid / 2;
PRICE_FEEDS[marketId] = price;
}
function onclose () {
function onclose() {
setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds);
}
}
@@ -618,9 +620,9 @@ async function chainlinkSetup(chainlinkMarketAddress) {
// get inital price
const response = await provider.latestRoundData();
PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals;
PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals;
} catch (e) {
throw new Error ("Error while setting up chainlink for "+address+", Error: "+e);
throw new Error("Error while setting up chainlink for " + address + ", Error: " + e);
}
});
await Promise.all(results);
@@ -632,14 +634,14 @@ async function chainlinkUpdate() {
await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => {
const [provider, decimals] = CHAINLINK_PROVIDERS[key];
const response = await provider.latestRoundData();
PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals;
PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals;
}));
chainlink_error_counter = 0;
} catch (err) {
chainlink_error_counter += 1;
console.log(`Failed to update chainlink, retry: ${err.message}`);
if(chainlink_error_counter > 4) {
throw new Error ("Failed to update chainlink since 150 seconds!")
if (chainlink_error_counter > 4) {
throw new Error("Failed to update chainlink since 150 seconds!")
}
}
}
@@ -649,39 +651,39 @@ async function uniswapV3Setup(uniswapV3Address) {
try {
const IUniswapV3PoolABI = JSON.parse(fs.readFileSync('ABIs/IUniswapV3Pool.abi'));
const ERC20ABI = JSON.parse(fs.readFileSync('ABIs/ERC20.abi'));
const provider = new ethers.Contract(address, IUniswapV3PoolABI, ethersProvider);
let [
slot0,
addressToken0,
addressToken1
] = await Promise.all ([
provider.slot0(),
provider.token0(),
provider.token1()
slot0,
addressToken0,
addressToken1
] = await Promise.all([
provider.slot0(),
provider.token0(),
provider.token1()
]);
const tokenProvier0 = new ethers.Contract(addressToken0, ERC20ABI, ethersProvider);
const tokenProvier1 = new ethers.Contract(addressToken1, ERC20ABI, ethersProvider);
let [
decimals0,
decimals1
] = await Promise.all ([
tokenProvier0.decimals(),
tokenProvier1.decimals()
decimals0,
decimals1
] = await Promise.all([
tokenProvier0.decimals(),
tokenProvier1.decimals()
]);
const key = 'uniswapv3:' + address;
const decimalsRatio = (10**decimals0 / 10**decimals1);
const decimalsRatio = (10 ** decimals0 / 10 ** decimals1);
UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio];
// get inital price
const price = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192);
const price = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / (2 ** 192);
PRICE_FEEDS[key] = price;
} catch (e) {
throw new Error ("Error while setting up uniswapV3 for "+address+", Error: "+e);
throw new Error("Error while setting up uniswapV3 for " + address + ", Error: " + e);
}
});
await Promise.all(results);
@@ -693,7 +695,7 @@ async function uniswapV3Update() {
await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => {
const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key];
const slot0 = await provider.slot0();
PRICE_FEEDS[key] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192);
PRICE_FEEDS[key] = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / (2 ** 192);
}));
// reset error counter if successful
uniswap_error_counter = 0;
@@ -701,28 +703,31 @@ async function uniswapV3Update() {
uniswap_error_counter += 1;
console.log(`Failed to update uniswap, retry: ${err.message}`);
console.log(err.message);
if(uniswap_error_counter > 4) {
throw new Error ("Failed to update uniswap since 150 seconds!")
if (uniswap_error_counter > 4) {
throw new Error("Failed to update uniswap since 150 seconds!")
}
}
}
function indicateLiquidity (pairs = MM_CONFIG.pairs) {
for(const marketId in pairs) {
function indicateLiquidity(pairs = MM_CONFIG.pairs) {
for (const marketId in pairs) {
const mmConfig = pairs[marketId];
if(!mmConfig || !mmConfig.active) continue;
if (!mmConfig || !mmConfig.active) continue;
try {
validatePriceFeed(marketId);
} catch(e) {
console.error("Can not indicateLiquidity ("+marketId+") because: " + e);
} catch (e) {
console.error("Can not indicateLiquidity (" + marketId + ") because: " + e);
continue;
}
const marketInfo = MARKETS[marketId];
if (!marketInfo) continue;
const midPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary];
const midPrice = (mmConfig.invert)
? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary])
: PRICE_FEEDS[mmConfig.priceFeedPrimary];
if (!midPrice) continue;
const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry
@@ -739,8 +744,8 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) {
maxQuoteBalance = walletQuote;
}
});
const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals;
const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals;
const baseBalance = maxBaseBalance / 10 ** marketInfo.baseAsset.decimals;
const quoteBalance = maxQuoteBalance / 10 ** marketInfo.quoteAsset.decimals;
const maxSellSize = Math.min(baseBalance, mmConfig.maxSize);
const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize);
@@ -749,23 +754,23 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) {
const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice;
let buySplits = (usdQuoteBalance && usdQuoteBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4);
let sellSplits = (usdBaseBalance && usdBaseBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4);
if (usdQuoteBalance && usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10)
if (usdBaseBalance && usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10)
const liquidity = [];
for (let i=1; i <= buySplits; i++) {
const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/buySplits));
if ((['b','d']).includes(side)) {
for (let i = 1; i <= buySplits; i++) {
const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i / buySplits));
if ((['b', 'd']).includes(side)) {
liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]);
}
}
for (let i=1; i <= sellSplits; i++) {
const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/sellSplits));
if ((['s','d']).includes(side)) {
liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]);
}
}
for (let i = 1; i <= sellSplits; i++) {
const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i / sellSplits));
if ((['s', 'd']).includes(side)) {
liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]);
}
}
const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] };
try {
@@ -777,7 +782,7 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) {
}
}
function cancelLiquidity (chainId, marketId) {
function cancelLiquidity(chainId, marketId) {
const msg = { op: "indicateliq2", args: [chainId, marketId, []] };
try {
zigzagws.send(JSON.stringify(msg));
@@ -789,18 +794,18 @@ function cancelLiquidity (chainId, marketId) {
async function afterFill(chainId, orderId, wallet) {
const order = PAST_ORDER_LIST[orderId];
if(!order) { return; }
if (!order) { return; }
const marketId = order.marketId;
const mmConfig = MM_CONFIG.pairs[marketId];
if(!mmConfig) { return; }
if (!mmConfig) { return; }
// update account state from order
const account_state = wallet['account_state'].committed.balances;
const buyTokenParsed = syncProvider.tokenSet.parseToken (
const buyTokenParsed = syncProvider.tokenSet.parseToken(
order.buySymbol,
order.buyQuantity
);
const sellTokenParsed = syncProvider.tokenSet.parseToken (
const sellTokenParsed = syncProvider.tokenSet.parseToken(
order.sellSymbol,
order.sellQuantity
);
@@ -810,12 +815,12 @@ async function afterFill(chainId, orderId, wallet) {
const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance);
account_state[order.buySymbol] = (oldBuyTokenParsed.add(buyTokenParsed)).toString();
account_state[order.sellSymbol] = (oldSellTokenParsed.sub(sellTokenParsed)).toString();
const indicateMarket = {};
indicateMarket[marketId] = mmConfig;
if(mmConfig.delayAfterFill) {
if (mmConfig.delayAfterFill) {
let delayAfterFillMinSize
if(
if (
!Array.isArray(mmConfig.delayAfterFill) ||
!mmConfig.delayAfterFill[1]
) {
@@ -824,28 +829,28 @@ async function afterFill(chainId, orderId, wallet) {
delayAfterFillMinSize = mmConfig.delayAfterFill[1]
}
if(order.baseQuantity > delayAfterFillMinSize) {
if (order.baseQuantity > delayAfterFillMinSize) {
// no array -> old config
// or array and buyQuantity over minSize
mmConfig.active = false;
cancelLiquidity (chainId, marketId);
cancelLiquidity(chainId, marketId);
console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`);
setTimeout(() => {
mmConfig.active = true;
console.log(`Set ${marketId} active.`);
indicateLiquidity(indicateMarket);
}, mmConfig.delayAfterFill * 1000);
}
}, mmConfig.delayAfterFill * 1000);
}
}
// increaseSpreadAfterFill size might not be set
const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2])
const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2])
? mmConfig.increaseSpreadAfterFill[2]
: 0
if(
if (
mmConfig.increaseSpreadAfterFill &&
order.baseQuantity > increaseSpreadAfterFillMinSize
) {
const [spread, time] = mmConfig.increaseSpreadAfterFill;
mmConfig.minSpread = mmConfig.minSpread + spread;
@@ -859,10 +864,10 @@ async function afterFill(chainId, orderId, wallet) {
}
// changeSizeAfterFill size might not be set
const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2])
const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2])
? mmConfig.changeSizeAfterFill[2]
: 0
if(
if (
mmConfig.changeSizeAfterFill &&
order.baseQuantity > changeSizeAfterFillMinSize
) {
@@ -872,7 +877,7 @@ async function afterFill(chainId, orderId, wallet) {
indicateLiquidity(indicateMarket);
setTimeout(() => {
mmConfig.maxSize = mmConfig.maxSize - size;
console.log(`Changed ${marketId} maxSize by ${(size* (-1))}.`);
console.log(`Changed ${marketId} maxSize by ${(size * (-1))}.`);
indicateLiquidity(indicateMarket);
}, time * 1000);
}
@@ -888,7 +893,7 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti
const [baseSymbol, quoteSymbol] = marketId.split('-')
let baseQuantity, quoteQuantity;
if(sellSymbol === baseSymbol) {
if (sellSymbol === baseSymbol) {
baseQuantity = sellQuantity;
quoteQuantity = buyQuantity;
} else {
@@ -907,7 +912,7 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti
'sellQuantity': sellQuantity,
'buySymbol': buySymbol,
'buyQuantity': buyQuantity,
'expiry':expiry
'expiry': expiry
};
}
@@ -918,7 +923,7 @@ async function updateAccountState() {
WALLETS[accountId]['account_state'] = state;
})
});
} catch(err) {
} catch (err) {
// pass
}
}