update pay function

This commit is contained in:
2025-11-23 18:11:48 +08:00
parent b582de9bc2
commit 7b3907a3bd
9 changed files with 3229 additions and 91 deletions

View File

@@ -0,0 +1,492 @@
/**
* 积分系统服务
* 管理用户积分账户、交易、奖励等
*/
// ==================== 常量配置 ====================
export const CREDIT_CONFIG = {
INITIAL_BALANCE: 10000, // 初始积分
MIN_BALANCE: 100, // 最低保留余额(破产保护)
MAX_SINGLE_BET: 1000, // 单次下注上限
DAILY_BONUS: 100, // 每日签到奖励
CREATE_TOPIC_COST: 100, // 创建话题费用
};
// 积分账户存储(生产环境应使用数据库)
const userAccounts = new Map();
// 交易记录存储
const transactions = [];
// ==================== 账户管理 ====================
/**
* 获取用户账户
* @param {string} userId - 用户ID
* @returns {Object} 用户账户信息
*/
export const getUserAccount = (userId) => {
if (!userAccounts.has(userId)) {
// 首次访问,创建新账户
const newAccount = {
user_id: userId,
balance: CREDIT_CONFIG.INITIAL_BALANCE,
frozen: 0,
total: CREDIT_CONFIG.INITIAL_BALANCE,
total_earned: CREDIT_CONFIG.INITIAL_BALANCE,
total_spent: 0,
total_profit: 0,
active_positions: [],
stats: {
total_topics: 0,
win_count: 0,
loss_count: 0,
win_rate: 0,
best_profit: 0,
},
last_daily_bonus: null,
};
userAccounts.set(userId, newAccount);
}
return userAccounts.get(userId);
};
/**
* 更新用户账户
* @param {string} userId - 用户ID
* @param {Object} updates - 更新内容
*/
export const updateUserAccount = (userId, updates) => {
const account = getUserAccount(userId);
const updated = { ...account, ...updates };
userAccounts.set(userId, updated);
return updated;
};
/**
* 获取用户积分余额
* @param {string} userId - 用户ID
* @returns {number} 可用余额
*/
export const getBalance = (userId) => {
const account = getUserAccount(userId);
return account.balance;
};
/**
* 检查用户是否能支付
* @param {string} userId - 用户ID
* @param {number} amount - 金额
* @returns {boolean} 是否能支付
*/
export const canAfford = (userId, amount) => {
const account = getUserAccount(userId);
const afterBalance = account.balance - amount;
// 必须保留最低余额
return afterBalance >= CREDIT_CONFIG.MIN_BALANCE;
};
// ==================== 积分操作 ====================
/**
* 增加积分
* @param {string} userId - 用户ID
* @param {number} amount - 金额
* @param {string} reason - 原因
*/
export const addCredits = (userId, amount, reason = '系统增加') => {
const account = getUserAccount(userId);
const updated = {
balance: account.balance + amount,
total: account.total + amount,
total_earned: account.total_earned + amount,
};
updateUserAccount(userId, updated);
// 记录交易
logTransaction({
user_id: userId,
type: 'earn',
amount,
reason,
balance_after: updated.balance,
});
return updated;
};
/**
* 扣除积分
* @param {string} userId - 用户ID
* @param {number} amount - 金额
* @param {string} reason - 原因
* @throws {Error} 如果余额不足
*/
export const deductCredits = (userId, amount, reason = '系统扣除') => {
if (!canAfford(userId, amount)) {
throw new Error(`积分不足,需要${amount}积分,但只有${getBalance(userId)}积分`);
}
const account = getUserAccount(userId);
const updated = {
balance: account.balance - amount,
total_spent: account.total_spent + amount,
};
updateUserAccount(userId, updated);
// 记录交易
logTransaction({
user_id: userId,
type: 'spend',
amount: -amount,
reason,
balance_after: updated.balance,
});
return updated;
};
/**
* 冻结积分(席位占用)
* @param {string} userId - 用户ID
* @param {number} amount - 金额
*/
export const freezeCredits = (userId, amount) => {
const account = getUserAccount(userId);
if (account.balance < amount) {
throw new Error('可用余额不足');
}
const updated = {
balance: account.balance - amount,
frozen: account.frozen + amount,
};
updateUserAccount(userId, updated);
return updated;
};
/**
* 解冻积分
* @param {string} userId - 用户ID
* @param {number} amount - 金额
*/
export const unfreezeCredits = (userId, amount) => {
const account = getUserAccount(userId);
const updated = {
balance: account.balance + amount,
frozen: account.frozen - amount,
};
updateUserAccount(userId, updated);
return updated;
};
// ==================== 每日奖励 ====================
/**
* 领取每日签到奖励
* @param {string} userId - 用户ID
* @returns {Object} 奖励信息
*/
export const claimDailyBonus = (userId) => {
const account = getUserAccount(userId);
const today = new Date().toDateString();
// 检查是否已领取
if (account.last_daily_bonus === today) {
return {
success: false,
message: '今日已领取',
};
}
// 发放奖励
addCredits(userId, CREDIT_CONFIG.DAILY_BONUS, '每日签到');
// 更新领取时间
updateUserAccount(userId, { last_daily_bonus: today });
return {
success: true,
amount: CREDIT_CONFIG.DAILY_BONUS,
message: `获得${CREDIT_CONFIG.DAILY_BONUS}积分`,
};
};
/**
* 检查今天是否已签到
* @param {string} userId - 用户ID
* @returns {boolean}
*/
export const hasClaimedToday = (userId) => {
const account = getUserAccount(userId);
const today = new Date().toDateString();
return account.last_daily_bonus === today;
};
// ==================== 持仓管理 ====================
/**
* 添加持仓
* @param {string} userId - 用户ID
* @param {Object} position - 持仓信息
*/
export const addPosition = (userId, position) => {
const account = getUserAccount(userId);
const updated = {
active_positions: [...account.active_positions, position],
stats: {
...account.stats,
total_topics: account.stats.total_topics + 1,
},
};
updateUserAccount(userId, updated);
return updated;
};
/**
* 移除持仓
* @param {string} userId - 用户ID
* @param {string} positionId - 持仓ID
*/
export const removePosition = (userId, positionId) => {
const account = getUserAccount(userId);
const updated = {
active_positions: account.active_positions.filter((p) => p.id !== positionId),
};
updateUserAccount(userId, updated);
return updated;
};
/**
* 更新持仓
* @param {string} userId - 用户ID
* @param {string} positionId - 持仓ID
* @param {Object} updates - 更新内容
*/
export const updatePosition = (userId, positionId, updates) => {
const account = getUserAccount(userId);
const updated = {
active_positions: account.active_positions.map((p) =>
p.id === positionId ? { ...p, ...updates } : p
),
};
updateUserAccount(userId, updated);
return updated;
};
/**
* 获取用户持仓
* @param {string} userId - 用户ID
* @param {string} topicId - 话题ID可选
* @returns {Array} 持仓列表
*/
export const getUserPositions = (userId, topicId = null) => {
const account = getUserAccount(userId);
if (topicId) {
return account.active_positions.filter((p) => p.topic_id === topicId);
}
return account.active_positions;
};
// ==================== 统计更新 ====================
/**
* 记录胜利
* @param {string} userId - 用户ID
* @param {number} profit - 盈利金额
*/
export const recordWin = (userId, profit) => {
const account = getUserAccount(userId);
const newWinCount = account.stats.win_count + 1;
const totalGames = newWinCount + account.stats.loss_count;
const winRate = (newWinCount / totalGames) * 100;
const updated = {
total_profit: account.total_profit + profit,
stats: {
...account.stats,
win_count: newWinCount,
win_rate: winRate,
best_profit: Math.max(account.stats.best_profit, profit),
},
};
updateUserAccount(userId, updated);
return updated;
};
/**
* 记录失败
* @param {string} userId - 用户ID
* @param {number} loss - 损失金额
*/
export const recordLoss = (userId, loss) => {
const account = getUserAccount(userId);
const newLossCount = account.stats.loss_count + 1;
const totalGames = account.stats.win_count + newLossCount;
const winRate = (account.stats.win_count / totalGames) * 100;
const updated = {
total_profit: account.total_profit - loss,
stats: {
...account.stats,
loss_count: newLossCount,
win_rate: winRate,
},
};
updateUserAccount(userId, updated);
return updated;
};
// ==================== 排行榜 ====================
/**
* 获取积分排行榜
* @param {number} limit - 返回数量
* @returns {Array} 排行榜数据
*/
export const getLeaderboard = (limit = 100) => {
const accounts = Array.from(userAccounts.values());
return accounts
.sort((a, b) => b.total - a.total)
.slice(0, limit)
.map((account, index) => ({
rank: index + 1,
user_id: account.user_id,
total: account.total,
total_profit: account.total_profit,
win_rate: account.stats.win_rate,
}));
};
/**
* 获取用户排名
* @param {string} userId - 用户ID
* @returns {number} 排名
*/
export const getUserRank = (userId) => {
const leaderboard = getLeaderboard(1000);
const index = leaderboard.findIndex((item) => item.user_id === userId);
return index >= 0 ? index + 1 : -1;
};
// ==================== 交易记录 ====================
/**
* 记录交易
* @param {Object} transaction - 交易信息
*/
const logTransaction = (transaction) => {
const record = {
id: `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date().toISOString(),
...transaction,
};
transactions.push(record);
return record;
};
/**
* 获取用户交易记录
* @param {string} userId - 用户ID
* @param {number} limit - 返回数量
* @returns {Array} 交易记录
*/
export const getUserTransactions = (userId, limit = 50) => {
return transactions
.filter((tx) => tx.user_id === userId)
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
.slice(0, limit);
};
// ==================== 批量操作 ====================
/**
* 批量发放积分(如活动奖励)
* @param {Array} recipients - [{user_id, amount, reason}]
*/
export const batchAddCredits = (recipients) => {
const results = recipients.map(({ user_id, amount, reason }) => {
try {
return {
user_id,
success: true,
account: addCredits(user_id, amount, reason),
};
} catch (error) {
return {
user_id,
success: false,
error: error.message,
};
}
});
return results;
};
// ==================== 导出所有功能 ====================
export default {
CREDIT_CONFIG,
// 账户管理
getUserAccount,
updateUserAccount,
getBalance,
canAfford,
// 积分操作
addCredits,
deductCredits,
freezeCredits,
unfreezeCredits,
// 每日奖励
claimDailyBonus,
hasClaimedToday,
// 持仓管理
addPosition,
removePosition,
updatePosition,
getUserPositions,
// 统计更新
recordWin,
recordLoss,
// 排行榜
getLeaderboard,
getUserRank,
// 交易记录
getUserTransactions,
// 批量操作
batchAddCredits,
};

View 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,
};