import Logs from "../libs/logs.js"; import { platformGet, platformPost } from "../libs/platformRequest.js"; import pinnacleSdk from "../libs/pinnacleSdk.js"; import polymarketSdk from "../libs/polymarketSdk.js"; import eventSolutions from '../triangle/eventSolutions.js'; const MAKER_FEE_RATE = 0.03; const MAKER_REBATE_RATE = 0.25; const MIN_PROFIT_RATE = -1; const PINNACLE_MIN_RISK_STAKE = 1; /** * 精确浮点数字 * @param {number} number * @param {number} x * @returns {number} */ const fixFloat = (number, x=3) => { return parseFloat(number.toFixed(x)); } /** * 计算符合比例的最大和最小数组 */ const findMaxMinGroup = (ratios, minVals=10, maxVals=1000) => { const n = ratios.length; // 1. 计算比例总和 & 归一化比例 const totalRatio = ratios.reduce((a, b) => a + b, 0); const proportions = ratios.map(r => r / totalRatio); // 2. 计算每个位置允许的最大/最小倍数 let maxPossibleScale = Infinity; let minPossibleScale = 0; // 如果允许0,通常从0开始 for (let i = 0; i < n; i++) { // 上限约束 if (proportions[i] > 0) { maxPossibleScale = Math.min(maxPossibleScale, maxVals[i] / proportions[i]); } // 下限约束(如果 minVals[i] > 0 才有意义) if (proportions[i] > 0 && minVals[i] > 0) { minPossibleScale = Math.max(minPossibleScale, minVals[i] / proportions[i]); } } // 3. 最终取值 const maxGroup = proportions.map(p => p * maxPossibleScale); const minGroup = proportions.map(p => p * Math.max(minPossibleScale, 0)); return { maxGroup: maxGroup.map(v => fixFloat(v)), // 可控制精度 minGroup: minGroup.map(v => fixFloat(v)), proportions: proportions.map(v => fixFloat(v)), scaleForMax: fixFloat(maxPossibleScale), scaleForMin: fixFloat(minPossibleScale) }; } /** * 解析吃单手续费比 * 固定金额吃卖单时,手续费比约等于 费率*(1-价格) * @param {*} price * @returns {number} */ const parseAskFee = (price) => { return fixFloat(100 * MAKER_FEE_RATE * (1 - price), 4); } /** * 解析挂单返佣比 * 固定金额挂买单时,返佣比约等于 费率*(1-价格)*返佣比例 * @param {*} price * @returns {number} */ const parseBidRebate = (price) => { return fixFloat(100 * MAKER_FEE_RATE * (1 - price) * MAKER_REBATE_RATE, 4); } /** * 获取polymarket盘口信息 * @param {*} ior * @param {*} id * @returns */ const getPolymarketIorInfo = async (ior, id) => { return platformGet(`http://127.0.0.1:9021/api/trading/get_ior_info/${id}/${ior}`) .then(res => res.data) .catch(err => { Logs.out('get polymarket ior info error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * 获取pinnacle盘口信息 * @param {*} ior * @param {*} id * @returns */ const getPinnacleIorInfo = async (ior, id) => { return platformGet(`https://cb.long.bid/api/trading/get_ior_info/${id}/${ior}`) .then(res => res.data) .catch(err => { Logs.out('get pinnacle ior info error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * 获取平台盘口id信息 */ export const getPlatformIorInfo = async (ior, platform, id) => { const getInfo = { polymarket() { return getPolymarketIorInfo(ior, id); }, pinnacle() { return getPinnacleIorInfo(ior, id); }, obsports() { return {} }, huangguan() { return {} } } const result = await getInfo[platform]?.(); Logs.outDev('getPlatformIorInfo', { ior, platform, id }, result); return result; } /** * 获取polymarket盘口详细信息 */ const getPolymarketIorDetailInfo = async (info) => { // Logs.outDev('get polymarket ior detail info', info); const { id } = info; return polymarketSdk.getOrderBook(id) .catch(err => { Logs.out('get polymarket ior detail info error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * 获取pinnacle盘口详细信息 */ const getPinnacleIorDetailInfo = async (info, channel) => { // Logs.outDev('get pinnacle ior detail info', { info, channel }); return pinnacleSdk.getLineInfo({ info, channel }) .catch(err => { Logs.out('get pinnacle ior detail info error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * 获取平台盘口详细信息 */ export const getPlatformIorsDetailInfo = async (ior, platform, id) => { const info = await getPlatformIorInfo(ior, platform, id); if (!info) { return Promise.reject(new Error('platform ior info not found', { cause: 400 })); } const getInfo = { polymarket() { return getPolymarketIorDetailInfo(info); }, pinnacle() { return getPinnacleIorDetailInfo(info); }, obsports() { return {} }, huangguan() { return {} } } return getInfo[platform]?.(); } /** * 根据最新赔率获取策略 */ export const getSolutionByLatestIors = (iorsInfo, cross_type, retry=false) => { const askIndex = +retry; const iorsValues = iorsInfo.map(item => { if (item.asks) { const bestAsk = [...item.asks].sort((a, b) => a.price - b.price)[askIndex]; const bestPrice = bestAsk.price - item.tick_size; const value = fixFloat(1 / bestPrice, 3); const maxStake = 99999; const minStake = fixFloat(item.min_order_size * bestAsk.price); const tickSize = +item.tick_size; const negRisk = item.neg_risk; const rebate = parseBidRebate(bestPrice); const rebateType = 1; return { value, maxStake, minStake, bestPrice, tickSize, negRisk, rebate, rebateType }; } else if (item.info) { const value = item.info.price; const maxStake = Math.floor(item.info.maxRiskStake); const minStake = Math.ceil(PINNACLE_MIN_RISK_STAKE > 0 ? PINNACLE_MIN_RISK_STAKE : item.info.minRiskStake); const rebateType = 1; return { value, maxStake, minStake, rebateType }; } else { return {}; } }); const nullIndex = iorsValues.findIndex(item => item?.value == null); if (nullIndex >= 0) { return { error: `IORS_NULL_VALUE_AT_INDEX_${nullIndex}_RETRY_${askIndex}`, data: iorsInfo }; } const baseIndex = iorsValues.reduce((minIdx, cur, idx) => cur.value < iorsValues[minIdx].value ? idx : minIdx, 0); if (iorsValues.length === 2) { iorsValues.push({ value: 1, maxStake: 0, minStake: 0 }); } const betInfo = { cross_type, base_index: baseIndex, base_stake: 10000, odds_side_a: fixFloat(iorsValues[0].value - 1), odds_side_b: fixFloat(iorsValues[1].value - 1), odds_side_c: fixFloat(iorsValues[2].value - 1), rebate_side_a: fixFloat(((iorsValues[0].rebate ?? 0) / 100), 6), rebate_side_b: fixFloat(((iorsValues[1].rebate ?? 0) / 100), 6), rebate_side_c: fixFloat(((iorsValues[2].rebate ?? 0) / 100), 6), rebate_type_side_a: iorsValues[0].rebateType ?? 0, rebate_type_side_b: iorsValues[1].rebateType ?? 0, rebate_type_side_c: iorsValues[2].rebateType ?? 0, }; const sol = eventSolutions(betInfo, true); const { win_average, win_profit_rate, gold_side_a, gold_side_b, gold_side_c } = sol; if (win_profit_rate < MIN_PROFIT_RATE) { Logs.outDev('win_profit_rate is less than profit rate limit', sol, iorsValues, iorsInfo, cross_type); return { error: `WIN_PROFIT_RATE_LESS_THAN_MIN_PROFIT_RATE_RETRY_${askIndex}`, data: { sol, iorsValues, iorsInfo } }; } const goldRatios = [gold_side_a, gold_side_b]; if (gold_side_c) { goldRatios.push(gold_side_c); } const minVals = iorsValues.map(item => item.minStake); const maxVals = iorsValues.map(item => item.maxStake); const stakeLimit = findMaxMinGroup(goldRatios, minVals, maxVals); const { scaleForMax, scaleForMin } = stakeLimit; if (scaleForMax < scaleForMin) { Logs.outDev('scaleForMax is less than scaleForMin'); if (!retry) { return getSolutionByLatestIors(iorsInfo, cross_type, true); } else { return { error: `NO_ENOUGH_STAKE_SIZE_TO_BET_RETRY_${askIndex}`, data: { sol, iorsValues, iorsInfo } }; } } const winLimit = { max: fixFloat(win_average * scaleForMax / (gold_side_a + gold_side_b + gold_side_c)), min: fixFloat(win_average * scaleForMin / (gold_side_a + gold_side_b + gold_side_c)), } return { sol, iorsValues, stakeLimit, winLimit }; } /** * 创建Polymarket限价挂单 买单 */ export const createPolymarketLimitBuyOrder = async ({ tokenID, price, size, tickSize = "0.01", negRisk = false } = {}) => { return polymarketSdk.createLimitOrder({ tokenID, price, size, tickSize, negRisk }) .catch(err => { Logs.out('create polymarket limit buy order error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * 查询Polymarket单个订单 * @param {Object} options * @param {string} options.orderID 订单ID * @returns {Promise} */ export const getPolymarketOrder = async ({ orderID } = {}) => { if (!orderID) { throw new Error('orderID is required', { cause: 400 }); } return polymarketSdk.getOrder(orderID) .catch(err => { Logs.out('get polymarket order error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * 查询Polymarket开放订单 * @param {Object} options * @param {string} options.id 订单ID * @param {string} options.market 市场ID * @param {string} options.asset_id token/asset ID * @param {boolean} options.only_first_page 是否只查询第一页 * @param {string} options.next_cursor 分页cursor * @returns {Promise} */ export const getPolymarketOpenOrders = async ({ id, market, asset_id, only_first_page = false, next_cursor, } = {}) => { return polymarketSdk.getOpenOrders({ id, market, asset_id, only_first_page, next_cursor, }) .catch(err => { Logs.out('get polymarket open orders error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * 获取Polymarket钱包余额信息 * @param {*} param0 * @returns */ export const getPolymarketBalanceAllowance = async ({ wallet = "both" } = {}) => { if (!wallet || !["both", "proxy", "deposit"].includes(wallet)) { throw new Error('invalid wallet', { cause: 400 }); } return polymarketSdk.getBalanceAllowance({ wallet }) .catch(err => { Logs.out('get polymarket balance allowance error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * 获取Pinnacle账户余额信息 */ export const getPinnacleBalance = async ({ channel } = {}) => { return pinnacleSdk.getAccountBalance(channel) .then(balance => ({ ...balance, account: process.env.PINNACLE_USERNAME, })) .catch(err => { Logs.out('get pinnacle balance error', err.message); Logs.errDev(err); return Promise.reject(err); }); } /** * Polymarket钱包之间转账 * 通过HTTP调用polymarket项目接口,server项目不直接依赖polymarket项目代码 * @param {Object} options * @param {string|number} options.amount 转账数量 * @param {"proxy"|"deposit"} options.from 来源钱包类型 * @param {"proxy"|"deposit"} options.to 目标钱包类型 * @returns {Promise} */ export const transferPolymarketWallet = async ({ amount, from, to } = {}) => { if (!amount) { throw new Error('amount is required', { cause: 400 }); } if (!from || !["proxy", "deposit"].includes(from)) { throw new Error('from is required', { cause: 400 }); } if (!to || !["proxy", "deposit"].includes(to)) { throw new Error('to is required', { cause: 400 }); } if (from === to) { throw new Error('from and to cannot be the same', { cause: 400 }); } return polymarketSdk.transferWallet({ amount, from, to }) .catch(err => { Logs.out('transfer polymarket wallet error', err.message); Logs.errDev(err); return Promise.reject(err); }); }