update pay function
This commit is contained in:
738
src/services/predictionMarketService.js
Normal file
738
src/services/predictionMarketService.js
Normal file
@@ -0,0 +1,738 @@
|
||||
/**
|
||||
* 预测市场服务
|
||||
* 核心功能:话题管理、席位交易、动态定价、领主系统、奖池分配
|
||||
*/
|
||||
|
||||
import {
|
||||
addCredits,
|
||||
deductCredits,
|
||||
canAfford,
|
||||
addPosition,
|
||||
removePosition,
|
||||
updatePosition,
|
||||
getUserPositions,
|
||||
recordWin,
|
||||
recordLoss,
|
||||
CREDIT_CONFIG,
|
||||
} from './creditSystemService';
|
||||
|
||||
// ==================== 常量配置 ====================
|
||||
|
||||
export const MARKET_CONFIG = {
|
||||
MAX_SEATS_PER_SIDE: 5, // 每个方向最多5个席位
|
||||
TAX_RATE: 0.02, // 交易税率 2%
|
||||
MIN_PRICE: 50, // 最低价格
|
||||
MAX_PRICE: 950, // 最高价格
|
||||
BASE_PRICE: 500, // 基础价格
|
||||
};
|
||||
|
||||
// 话题存储(生产环境应使用Elasticsearch)
|
||||
const topics = new Map();
|
||||
|
||||
// 席位存储
|
||||
const positions = new Map();
|
||||
|
||||
// 交易记录
|
||||
const trades = [];
|
||||
|
||||
// ==================== 动态定价算法 ====================
|
||||
|
||||
/**
|
||||
* 计算当前价格(简化版AMM)
|
||||
* @param {number} yesShares - Yes方总份额
|
||||
* @param {number} noShares - No方总份额
|
||||
* @returns {Object} {yes: price, no: price}
|
||||
*/
|
||||
export const calculatePrice = (yesShares, noShares) => {
|
||||
const totalShares = yesShares + noShares;
|
||||
|
||||
if (totalShares === 0) {
|
||||
// 初始状态,双方价格相同
|
||||
return {
|
||||
yes: MARKET_CONFIG.BASE_PRICE,
|
||||
no: MARKET_CONFIG.BASE_PRICE,
|
||||
};
|
||||
}
|
||||
|
||||
// 概率加权定价
|
||||
const yesProb = yesShares / totalShares;
|
||||
const noProb = noShares / totalShares;
|
||||
|
||||
// 价格 = 概率 * 1000,限制在 [MIN_PRICE, MAX_PRICE]
|
||||
const yesPrice = Math.max(
|
||||
MARKET_CONFIG.MIN_PRICE,
|
||||
Math.min(MARKET_CONFIG.MAX_PRICE, yesProb * 1000)
|
||||
);
|
||||
|
||||
const noPrice = Math.max(
|
||||
MARKET_CONFIG.MIN_PRICE,
|
||||
Math.min(MARKET_CONFIG.MAX_PRICE, noProb * 1000)
|
||||
);
|
||||
|
||||
return { yes: yesPrice, no: noPrice };
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算购买成本(含滑点)
|
||||
* @param {number} currentShares - 当前份额
|
||||
* @param {number} otherShares - 对手方份额
|
||||
* @param {number} buyAmount - 购买数量
|
||||
* @returns {number} 总成本
|
||||
*/
|
||||
export const calculateBuyCost = (currentShares, otherShares, buyAmount) => {
|
||||
let totalCost = 0;
|
||||
let tempShares = currentShares;
|
||||
|
||||
// 模拟逐步购买,累计成本
|
||||
for (let i = 0; i < buyAmount; i++) {
|
||||
tempShares += 1;
|
||||
const prices = calculatePrice(tempShares, otherShares);
|
||||
// 假设购买的是yes方
|
||||
totalCost += prices.yes;
|
||||
}
|
||||
|
||||
return totalCost;
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算卖出收益(含滑点)
|
||||
* @param {number} currentShares - 当前份额
|
||||
* @param {number} otherShares - 对手方份额
|
||||
* @param {number} sellAmount - 卖出数量
|
||||
* @returns {number} 总收益
|
||||
*/
|
||||
export const calculateSellRevenue = (currentShares, otherShares, sellAmount) => {
|
||||
let totalRevenue = 0;
|
||||
let tempShares = currentShares;
|
||||
|
||||
// 模拟逐步卖出,累计收益
|
||||
for (let i = 0; i < sellAmount; i++) {
|
||||
const prices = calculatePrice(tempShares, otherShares);
|
||||
totalRevenue += prices.yes;
|
||||
tempShares -= 1;
|
||||
}
|
||||
|
||||
return totalRevenue;
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算交易税
|
||||
* @param {number} amount - 交易金额
|
||||
* @returns {number} 税费
|
||||
*/
|
||||
export const calculateTax = (amount) => {
|
||||
return Math.floor(amount * MARKET_CONFIG.TAX_RATE);
|
||||
};
|
||||
|
||||
// ==================== 话题管理 ====================
|
||||
|
||||
/**
|
||||
* 创建预测话题
|
||||
* @param {Object} topicData - 话题数据
|
||||
* @returns {Object} 创建的话题
|
||||
*/
|
||||
export const createTopic = (topicData) => {
|
||||
const { author_id, title, description, category, tags, deadline, settlement_date } = topicData;
|
||||
|
||||
// 扣除创建费用
|
||||
deductCredits(author_id, CREDIT_CONFIG.CREATE_TOPIC_COST, '创建预测话题');
|
||||
|
||||
const topic = {
|
||||
id: `topic_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
type: 'prediction',
|
||||
|
||||
// 基础信息
|
||||
title,
|
||||
description,
|
||||
category,
|
||||
tags: tags || [],
|
||||
|
||||
// 作者信息
|
||||
author_id,
|
||||
author_name: topicData.author_name,
|
||||
author_avatar: topicData.author_avatar,
|
||||
|
||||
// 时间管理
|
||||
created_at: new Date().toISOString(),
|
||||
deadline: deadline || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 默认7天
|
||||
settlement_date: settlement_date || new Date(Date.now() + 8 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
status: 'active',
|
||||
|
||||
// 预测选项
|
||||
options: [
|
||||
{ id: 'yes', label: '看涨 / Yes', color: '#48BB78' },
|
||||
{ id: 'no', label: '看跌 / No', color: '#F56565' },
|
||||
],
|
||||
|
||||
// 市场数据
|
||||
total_pool: CREDIT_CONFIG.CREATE_TOPIC_COST, // 创建费用进入奖池
|
||||
tax_rate: MARKET_CONFIG.TAX_RATE,
|
||||
|
||||
// 席位数据
|
||||
positions: {
|
||||
yes: {
|
||||
seats: [],
|
||||
total_shares: 0,
|
||||
current_price: MARKET_CONFIG.BASE_PRICE,
|
||||
lord_id: null,
|
||||
},
|
||||
no: {
|
||||
seats: [],
|
||||
total_shares: 0,
|
||||
current_price: MARKET_CONFIG.BASE_PRICE,
|
||||
lord_id: null,
|
||||
},
|
||||
},
|
||||
|
||||
// 交易统计
|
||||
stats: {
|
||||
total_volume: 0,
|
||||
total_transactions: 0,
|
||||
unique_traders: new Set(),
|
||||
},
|
||||
|
||||
// 结果
|
||||
settlement: {
|
||||
result: null,
|
||||
evidence: null,
|
||||
settled_by: null,
|
||||
settled_at: null,
|
||||
},
|
||||
};
|
||||
|
||||
topics.set(topic.id, topic);
|
||||
return topic;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取话题详情
|
||||
* @param {string} topicId - 话题ID
|
||||
* @returns {Object} 话题详情
|
||||
*/
|
||||
export const getTopic = (topicId) => {
|
||||
return topics.get(topicId);
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新话题
|
||||
* @param {string} topicId - 话题ID
|
||||
* @param {Object} updates - 更新内容
|
||||
*/
|
||||
export const updateTopic = (topicId, updates) => {
|
||||
const topic = getTopic(topicId);
|
||||
const updated = { ...topic, ...updates };
|
||||
topics.set(topicId, updated);
|
||||
return updated;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有话题列表
|
||||
* @param {Object} filters - 筛选条件
|
||||
* @returns {Array} 话题列表
|
||||
*/
|
||||
export const getTopics = (filters = {}) => {
|
||||
let topicList = Array.from(topics.values());
|
||||
|
||||
// 按状态筛选
|
||||
if (filters.status) {
|
||||
topicList = topicList.filter((t) => t.status === filters.status);
|
||||
}
|
||||
|
||||
// 按分类筛选
|
||||
if (filters.category) {
|
||||
topicList = topicList.filter((t) => t.category === filters.category);
|
||||
}
|
||||
|
||||
// 排序
|
||||
const sortBy = filters.sortBy || 'created_at';
|
||||
topicList.sort((a, b) => {
|
||||
if (sortBy === 'created_at') {
|
||||
return new Date(b.created_at) - new Date(a.created_at);
|
||||
}
|
||||
if (sortBy === 'total_pool') {
|
||||
return b.total_pool - a.total_pool;
|
||||
}
|
||||
if (sortBy === 'total_volume') {
|
||||
return b.stats.total_volume - a.stats.total_volume;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return topicList;
|
||||
};
|
||||
|
||||
// ==================== 席位管理 ====================
|
||||
|
||||
/**
|
||||
* 创建席位
|
||||
* @param {Object} positionData - 席位数据
|
||||
* @returns {Object} 创建的席位
|
||||
*/
|
||||
const createPosition = (positionData) => {
|
||||
const position = {
|
||||
id: `pos_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
...positionData,
|
||||
acquired_at: new Date().toISOString(),
|
||||
last_traded_at: new Date().toISOString(),
|
||||
is_lord: false,
|
||||
};
|
||||
|
||||
positions.set(position.id, position);
|
||||
return position;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取席位
|
||||
* @param {string} positionId - 席位ID
|
||||
* @returns {Object} 席位信息
|
||||
*/
|
||||
export const getPosition = (positionId) => {
|
||||
return positions.get(positionId);
|
||||
};
|
||||
|
||||
/**
|
||||
* 分配席位(取份额最高的前5名)
|
||||
* @param {Array} allPositions - 所有持仓
|
||||
* @returns {Array} 席位列表
|
||||
*/
|
||||
const allocateSeats = (allPositions) => {
|
||||
// 按份额排序
|
||||
const sorted = [...allPositions].sort((a, b) => b.shares - a.shares);
|
||||
|
||||
// 取前5名
|
||||
return sorted.slice(0, MARKET_CONFIG.MAX_SEATS_PER_SIDE);
|
||||
};
|
||||
|
||||
/**
|
||||
* 确定领主(份额最多的人)
|
||||
* @param {Array} seats - 席位列表
|
||||
* @returns {string|null} 领主用户ID
|
||||
*/
|
||||
const determineLord = (seats) => {
|
||||
if (seats.length === 0) return null;
|
||||
|
||||
const lord = seats.reduce((max, seat) => (seat.shares > max.shares ? seat : max));
|
||||
|
||||
return lord.holder_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新领主标识
|
||||
* @param {string} topicId - 话题ID
|
||||
* @param {string} optionId - 选项ID
|
||||
*/
|
||||
const updateLordStatus = (topicId, optionId) => {
|
||||
const topic = getTopic(topicId);
|
||||
const sideData = topic.positions[optionId];
|
||||
|
||||
// 重新分配席位
|
||||
const allPositions = Array.from(positions.values()).filter(
|
||||
(p) => p.topic_id === topicId && p.option_id === optionId
|
||||
);
|
||||
|
||||
const seats = allocateSeats(allPositions);
|
||||
const lordId = determineLord(seats);
|
||||
|
||||
// 更新所有席位的领主标识
|
||||
allPositions.forEach((position) => {
|
||||
const isLord = position.holder_id === lordId;
|
||||
positions.set(position.id, { ...position, is_lord: isLord });
|
||||
});
|
||||
|
||||
// 更新话题数据
|
||||
updateTopic(topicId, {
|
||||
positions: {
|
||||
...topic.positions,
|
||||
[optionId]: {
|
||||
...sideData,
|
||||
seats,
|
||||
lord_id: lordId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return lordId;
|
||||
};
|
||||
|
||||
// ==================== 交易执行 ====================
|
||||
|
||||
/**
|
||||
* 购买席位
|
||||
* @param {Object} tradeData - 交易数据
|
||||
* @returns {Object} 交易结果
|
||||
*/
|
||||
export const buyPosition = (tradeData) => {
|
||||
const { user_id, user_name, user_avatar, topic_id, option_id, shares } = tradeData;
|
||||
|
||||
// 验证
|
||||
const topic = getTopic(topic_id);
|
||||
if (!topic) throw new Error('话题不存在');
|
||||
if (topic.status !== 'active') throw new Error('话题已关闭交易');
|
||||
if (topic.author_id === user_id) throw new Error('不能参与自己发起的话题');
|
||||
|
||||
// 检查购买上限
|
||||
if (shares * MARKET_CONFIG.BASE_PRICE > CREDIT_CONFIG.MAX_SINGLE_BET) {
|
||||
throw new Error(`单次购买上限为${CREDIT_CONFIG.MAX_SINGLE_BET}积分`);
|
||||
}
|
||||
|
||||
// 获取当前市场数据
|
||||
const sideData = topic.positions[option_id];
|
||||
const otherOptionId = option_id === 'yes' ? 'no' : 'yes';
|
||||
const otherSideData = topic.positions[otherOptionId];
|
||||
|
||||
// 计算成本
|
||||
const cost = calculateBuyCost(sideData.total_shares, otherSideData.total_shares, shares);
|
||||
const tax = calculateTax(cost);
|
||||
const totalCost = cost + tax;
|
||||
|
||||
// 检查余额
|
||||
if (!canAfford(user_id, totalCost)) {
|
||||
throw new Error(`积分不足,需要${totalCost}积分`);
|
||||
}
|
||||
|
||||
// 扣除积分
|
||||
deductCredits(user_id, totalCost, `购买预测席位 - ${topic.title}`);
|
||||
|
||||
// 税费进入奖池
|
||||
updateTopic(topic_id, {
|
||||
total_pool: topic.total_pool + tax,
|
||||
stats: {
|
||||
...topic.stats,
|
||||
total_volume: topic.stats.total_volume + totalCost,
|
||||
total_transactions: topic.stats.total_transactions + 1,
|
||||
unique_traders: topic.stats.unique_traders.add(user_id),
|
||||
},
|
||||
});
|
||||
|
||||
// 查找用户是否已有该选项的席位
|
||||
let userPosition = Array.from(positions.values()).find(
|
||||
(p) => p.topic_id === topic_id && p.option_id === option_id && p.holder_id === user_id
|
||||
);
|
||||
|
||||
if (userPosition) {
|
||||
// 更新现有席位
|
||||
const newShares = userPosition.shares + shares;
|
||||
const newAvgCost = (userPosition.avg_cost * userPosition.shares + cost) / newShares;
|
||||
|
||||
positions.set(userPosition.id, {
|
||||
...userPosition,
|
||||
shares: newShares,
|
||||
avg_cost: newAvgCost,
|
||||
last_traded_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 更新用户账户持仓
|
||||
updatePosition(user_id, userPosition.id, {
|
||||
shares: newShares,
|
||||
avg_cost: newAvgCost,
|
||||
});
|
||||
} else {
|
||||
// 创建新席位
|
||||
const newPosition = createPosition({
|
||||
topic_id,
|
||||
option_id,
|
||||
holder_id: user_id,
|
||||
holder_name: user_name,
|
||||
holder_avatar: user_avatar,
|
||||
shares,
|
||||
avg_cost: cost / shares,
|
||||
current_value: cost,
|
||||
unrealized_pnl: 0,
|
||||
});
|
||||
|
||||
// 添加到用户账户
|
||||
addPosition(user_id, {
|
||||
id: newPosition.id,
|
||||
topic_id,
|
||||
option_id,
|
||||
shares,
|
||||
avg_cost: cost / shares,
|
||||
});
|
||||
|
||||
userPosition = newPosition;
|
||||
}
|
||||
|
||||
// 更新话题席位数据
|
||||
updateTopic(topic_id, {
|
||||
positions: {
|
||||
...topic.positions,
|
||||
[option_id]: {
|
||||
...sideData,
|
||||
total_shares: sideData.total_shares + shares,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 更新价格
|
||||
const newPrices = calculatePrice(
|
||||
topic.positions[option_id].total_shares + shares,
|
||||
topic.positions[otherOptionId].total_shares
|
||||
);
|
||||
|
||||
updateTopic(topic_id, {
|
||||
positions: {
|
||||
...topic.positions,
|
||||
yes: { ...topic.positions.yes, current_price: newPrices.yes },
|
||||
no: { ...topic.positions.no, current_price: newPrices.no },
|
||||
},
|
||||
});
|
||||
|
||||
// 更新领主状态
|
||||
const newLordId = updateLordStatus(topic_id, option_id);
|
||||
|
||||
// 记录交易
|
||||
const trade = {
|
||||
id: `trade_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
topic_id,
|
||||
option_id,
|
||||
buyer_id: user_id,
|
||||
seller_id: null,
|
||||
type: 'buy',
|
||||
shares,
|
||||
price: cost / shares,
|
||||
total_cost: totalCost,
|
||||
tax,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
trades.push(trade);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
position: userPosition,
|
||||
trade,
|
||||
new_lord_id: newLordId,
|
||||
current_price: newPrices[option_id],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 卖出席位
|
||||
* @param {Object} tradeData - 交易数据
|
||||
* @returns {Object} 交易结果
|
||||
*/
|
||||
export const sellPosition = (tradeData) => {
|
||||
const { user_id, topic_id, option_id, shares } = tradeData;
|
||||
|
||||
// 验证
|
||||
const topic = getTopic(topic_id);
|
||||
if (!topic) throw new Error('话题不存在');
|
||||
if (topic.status !== 'active') throw new Error('话题已关闭交易');
|
||||
|
||||
// 查找用户席位
|
||||
const userPosition = Array.from(positions.values()).find(
|
||||
(p) => p.topic_id === topic_id && p.option_id === option_id && p.holder_id === user_id
|
||||
);
|
||||
|
||||
if (!userPosition) throw new Error('未持有该席位');
|
||||
if (userPosition.shares < shares) throw new Error('持有份额不足');
|
||||
|
||||
// 获取当前市场数据
|
||||
const sideData = topic.positions[option_id];
|
||||
const otherOptionId = option_id === 'yes' ? 'no' : 'yes';
|
||||
const otherSideData = topic.positions[otherOptionId];
|
||||
|
||||
// 计算收益
|
||||
const revenue = calculateSellRevenue(sideData.total_shares, otherSideData.total_shares, shares);
|
||||
const tax = calculateTax(revenue);
|
||||
const netRevenue = revenue - tax;
|
||||
|
||||
// 返还积分
|
||||
addCredits(user_id, netRevenue, `卖出预测席位 - ${topic.title}`);
|
||||
|
||||
// 税费进入奖池
|
||||
updateTopic(topic_id, {
|
||||
total_pool: topic.total_pool + tax,
|
||||
stats: {
|
||||
...topic.stats,
|
||||
total_volume: topic.stats.total_volume + revenue,
|
||||
total_transactions: topic.stats.total_transactions + 1,
|
||||
},
|
||||
});
|
||||
|
||||
// 更新席位
|
||||
const newShares = userPosition.shares - shares;
|
||||
|
||||
if (newShares === 0) {
|
||||
// 完全卖出,删除席位
|
||||
positions.delete(userPosition.id);
|
||||
removePosition(user_id, userPosition.id);
|
||||
} else {
|
||||
// 部分卖出,更新份额
|
||||
positions.set(userPosition.id, {
|
||||
...userPosition,
|
||||
shares: newShares,
|
||||
last_traded_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
updatePosition(user_id, userPosition.id, { shares: newShares });
|
||||
}
|
||||
|
||||
// 更新话题席位数据
|
||||
updateTopic(topic_id, {
|
||||
positions: {
|
||||
...topic.positions,
|
||||
[option_id]: {
|
||||
...sideData,
|
||||
total_shares: sideData.total_shares - shares,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 更新价格
|
||||
const newPrices = calculatePrice(
|
||||
topic.positions[option_id].total_shares - shares,
|
||||
topic.positions[otherOptionId].total_shares
|
||||
);
|
||||
|
||||
updateTopic(topic_id, {
|
||||
positions: {
|
||||
...topic.positions,
|
||||
yes: { ...topic.positions.yes, current_price: newPrices.yes },
|
||||
no: { ...topic.positions.no, current_price: newPrices.no },
|
||||
},
|
||||
});
|
||||
|
||||
// 更新领主状态
|
||||
const newLordId = updateLordStatus(topic_id, option_id);
|
||||
|
||||
// 记录交易
|
||||
const trade = {
|
||||
id: `trade_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
topic_id,
|
||||
option_id,
|
||||
buyer_id: null,
|
||||
seller_id: user_id,
|
||||
type: 'sell',
|
||||
shares,
|
||||
price: revenue / shares,
|
||||
total_cost: netRevenue,
|
||||
tax,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
trades.push(trade);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
trade,
|
||||
new_lord_id: newLordId,
|
||||
current_price: newPrices[option_id],
|
||||
};
|
||||
};
|
||||
|
||||
// ==================== 结算 ====================
|
||||
|
||||
/**
|
||||
* 结算话题
|
||||
* @param {string} topicId - 话题ID
|
||||
* @param {string} result - 结果 'yes' | 'no'
|
||||
* @param {string} evidence - 证据说明
|
||||
* @param {string} settledBy - 裁决者ID
|
||||
* @returns {Object} 结算结果
|
||||
*/
|
||||
export const settleTopic = (topicId, result, evidence, settledBy) => {
|
||||
const topic = getTopic(topicId);
|
||||
|
||||
if (!topic) throw new Error('话题不存在');
|
||||
if (topic.status === 'settled') throw new Error('话题已结算');
|
||||
|
||||
// 只有作者可以结算
|
||||
if (topic.author_id !== settledBy) throw new Error('无权结算');
|
||||
|
||||
// 获取获胜方和失败方
|
||||
const winningOption = result;
|
||||
const losingOption = result === 'yes' ? 'no' : 'yes';
|
||||
|
||||
const winners = Array.from(positions.values()).filter(
|
||||
(p) => p.topic_id === topicId && p.option_id === winningOption
|
||||
);
|
||||
|
||||
const losers = Array.from(positions.values()).filter(
|
||||
(p) => p.topic_id === topicId && p.option_id === losingOption
|
||||
);
|
||||
|
||||
// 分配奖池
|
||||
if (winners.length === 0) {
|
||||
// 无人获胜,奖池返还给作者
|
||||
addCredits(topic.author_id, topic.total_pool, '话题奖池返还');
|
||||
} else {
|
||||
// 计算获胜方总份额
|
||||
const totalWinningShares = winners.reduce((sum, p) => sum + p.shares, 0);
|
||||
|
||||
// 按份额分配
|
||||
winners.forEach((position) => {
|
||||
const share = position.shares / totalWinningShares;
|
||||
const reward = Math.floor(topic.total_pool * share);
|
||||
|
||||
// 返还本金 + 奖池分成
|
||||
const refund = Math.floor(position.avg_cost * position.shares);
|
||||
const total = refund + reward;
|
||||
|
||||
addCredits(position.holder_id, total, `预测获胜 - ${topic.title}`);
|
||||
|
||||
// 记录胜利
|
||||
recordWin(position.holder_id, reward);
|
||||
|
||||
// 删除席位
|
||||
positions.delete(position.id);
|
||||
removePosition(position.holder_id, position.id);
|
||||
});
|
||||
}
|
||||
|
||||
// 失败方损失本金
|
||||
losers.forEach((position) => {
|
||||
const loss = Math.floor(position.avg_cost * position.shares);
|
||||
|
||||
// 记录失败
|
||||
recordLoss(position.holder_id, loss);
|
||||
|
||||
// 删除席位
|
||||
positions.delete(position.id);
|
||||
removePosition(position.holder_id, position.id);
|
||||
});
|
||||
|
||||
// 更新话题状态
|
||||
updateTopic(topicId, {
|
||||
status: 'settled',
|
||||
settlement: {
|
||||
result,
|
||||
evidence,
|
||||
settled_by: settledBy,
|
||||
settled_at: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
winners_count: winners.length,
|
||||
losers_count: losers.length,
|
||||
total_distributed: topic.total_pool,
|
||||
};
|
||||
};
|
||||
|
||||
// ==================== 数据导出 ====================
|
||||
|
||||
export default {
|
||||
MARKET_CONFIG,
|
||||
|
||||
// 定价算法
|
||||
calculatePrice,
|
||||
calculateBuyCost,
|
||||
calculateSellRevenue,
|
||||
calculateTax,
|
||||
|
||||
// 话题管理
|
||||
createTopic,
|
||||
getTopic,
|
||||
updateTopic,
|
||||
getTopics,
|
||||
|
||||
// 席位管理
|
||||
getPosition,
|
||||
|
||||
// 交易
|
||||
buyPosition,
|
||||
sellPosition,
|
||||
|
||||
// 结算
|
||||
settleTopic,
|
||||
};
|
||||
Reference in New Issue
Block a user