4. 实现仓位管理模块
任务目标
实现一个仓位管理模块,支持开仓、平仓、加仓、减仓操作,并记录仓位方向(多/空)、数量、开仓价格等核心字段。模块以JavaScript类形式提供,可直接在Node.js或浏览器环境中运行验证。
核心实现
// PositionManager.js
class PositionManager {
constructor() {
this.positions = new Map(); // key: symbol, value: position object
}
// 内部:按symbol获取或初始化仓位
_getOrCreate(symbol) {
if (!this.positions.has(symbol)) {
this.positions.set(symbol, {
symbol,
direction: null, // 'long' | 'short' | null
quantity: 0, // 当前净持仓数量(正数=多,负数=空)
avgPrice: 0, // 平均开仓价格
realizedPnl: 0 // 已实现盈亏(简化,不计手续费)
});
}
return this.positions.get(symbol);
}
// 开仓:首次建立方向仓位
open(symbol, direction, quantity, price) {
if (quantity <= 0 || price <= 0) throw new Error('quantity和price必须为正数');
const pos = this._getOrCreate(symbol);
if (pos.quantity !== 0) throw new Error(`已有${pos.direction}仓位,请使用加仓或平仓`);
pos.direction = direction;
pos.quantity = direction === 'long' ? quantity : -quantity;
pos.avgPrice = price;
return { ...pos };
}
// 加仓:同方向增加数量,更新平均价格
add(symbol, direction, quantity, price) {
if (quantity <= 0 || price <= 0) throw new Error('quantity和price必须为正数');
const pos = this._getOrCreate(symbol);
if (pos.quantity === 0) throw new Error('无仓位,请先开仓');
if (pos.direction !== direction) throw new Error(`方向不一致:当前${pos.direction},试图加${direction}`);
const oldQty = Math.abs(pos.quantity);
const newQty = oldQty + quantity;
pos.avgPrice = (pos.avgPrice * oldQty + price * quantity) / newQty;
pos.quantity = direction === 'long' ? newQty : -newQty;
return { ...pos };
}
// 减仓:同方向减少数量,不改变平均价格
reduce(symbol, direction, quantity, price) {
if (quantity <= 0 || price <= 0) throw new Error('quantity和price必须为正数');
const pos = this._getOrCreate(symbol);
if (pos.quantity === 0) throw new Error('无仓位可减');
if (pos.direction !== direction) throw new Error(`方向不一致:当前${pos.direction},试图减${direction}`);
const currentQty = Math.abs(pos.quantity);
if (quantity > currentQty) throw new Error('减仓数量超过当前持仓');
// 计算已实现盈亏(简化:不考虑手续费)
const exitPrice = price;
const pnlPerUnit = direction === 'long' ? (exitPrice - pos.avgPrice) : (pos.avgPrice - exitPrice);
pos.realizedPnl += pnlPerUnit * quantity;
const remainQty = currentQty - quantity;
if (remainQty === 0) {
// 全部减完,清空仓位
pos.direction = null;
pos.quantity = 0;
pos.avgPrice = 0;
} else {
pos.quantity = direction === 'long' ? remainQty : -remainQty;
}
return { ...pos };
}
// 平仓:一次性清空仓位
close(symbol, price) {
const pos = this._getOrCreate(symbol);
if (pos.quantity === 0) throw new Error('无仓位可平');
const direction = pos.direction;
const qty = Math.abs(pos.quantity);
const pnlPerUnit = direction === 'long' ? (price - pos.avgPrice) : (pos.avgPrice - price);
pos.realizedPnl += pnlPerUnit * qty;
pos.direction = null;
pos.quantity = 0;
pos.avgPrice = 0;
return { ...pos };
}
// 查询当前仓位
getPosition(symbol) {
return this.positions.get(symbol) || null;
}
}
// 导出(Node.js环境)
if (typeof module !== 'undefined' && module.exports) {
module.exports = PositionManager;
}
运行与验证
将上述代码保存为 PositionManager.js,在Node.js中执行以下测试脚本验证功能:
// test.js
const PositionManager = require('./PositionManager');
const pm = new PositionManager();
// 1. 开多仓 2 ETH @ 3000
pm.open('ETH', 'long', 2, 3000);
let pos = pm.getPosition('ETH');
console.assert(pos.quantity === 2 && pos.avgPrice === 3000, '开仓失败');
// 2. 加仓 1 ETH @ 3300 → 平均价 (3000*2+3300*1)/3=3100
pm.add('ETH', 'long', 1, 3300);
pos = pm.getPosition('ETH');
console.assert(pos.quantity === 3 && pos.avgPrice === 3100, '加仓失败');
// 3. 减仓 1 ETH @ 3500 → 已实现盈亏 (3500-3100)*1=400
pm.reduce('ETH', 'long', 1, 3500);
pos = pm.getPosition('ETH');
console.assert(pos.quantity === 2 && pos.realizedPnl === 400, '减仓失败');
// 4. 平仓剩余 2 ETH @ 3600 → 已实现盈亏 400 + (3600-3100)*2=1400
pm.close('ETH', 3600);
pos = pm.getPosition('ETH');
console.assert(pos.quantity === 0 && pos.realizedPnl === 1400, '平仓失败');
// 5. 开空仓验证方向
pm.open('BTC', 'short', 1, 50000);
pos = pm.getPosition('BTC');
console.assert(pos.quantity === -1 && pos.direction === 'short', '开空失败');
console.log('✅ 所有断言通过');
运行命令:node test.js。若输出“✅ 所有断言通过”,说明模块功能正确。
验证要点
- 方向与数量一致性:多仓数量为正,空仓数量为负;加仓/减仓必须与当前方向一致。
- 平均价格计算:加仓后按加权平均更新;减仓和平仓不影响剩余仓位的平均价格。
- 已实现盈亏:减仓和平仓时按退出价格与平均价格的差累计,多仓为
退出价-均价,空仓为均价-退出价。 - 边界条件:无仓位时开仓成功;已有仓位时重复开仓抛错;减仓数量超过持仓抛错;平仓后仓位归零。