mirror of
https://github.com/ZigZagExchange/zksync-lite-market-maker.git
synced 2025-12-17 07:04:23 +01:00
add Invert price feed
This commit is contained in:
14
README.md
14
README.md
@@ -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).
|
||||
|
||||
459
marketmaker.js
459
marketmaker.js
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user