设计提案
设计 MT5 投资者门户、钱包与 IB 佣金:决定方案的五个问题
作者:曾祈荣 · · 阅读约 10 分钟
创始人描述这个平台时,讲的是一个个页面。投资者能看到自己的 MetaTrader 5 账户(交易历史、盈亏、余额、净值、保证金),还有一个钱包;介绍经纪商(IB)和他们的下级 IB 赚取佣金。听起来像是一个看板项目,外加一个支付页面。
页面是最便宜的部分。成本和风险由五个问题决定,它们都和页面无关,我会在报价之前先问清楚。这篇文章就是那场调研对话,写下来是为了让你自己也能回答这些问题。
问题一:你实际拥有什么样的 MT5 接入权限,对应哪些券商?
一个 Manager API 或 Web API 登录账号,只属于某一家券商的交易服务器。它能读取这家券商的账户,通常还仅限于券商开放的那些组别,除此之外什么都读不到。这个凭证只能由券商授予:我自己没有 MT5 服务器,也没有 Manager 授权。
如果你的投资者都在同一家券商交易,而且这家券商授予 Manager 权限,这部分很简单。如果他们分散在多家券商,每家券商就是一条独立的接入路线,路线有三种:
- 合作方接入:券商给你开一个 Manager API 或 Web API 登录账号,权限限定在你的客户所在的组别。
- 第三方云端桥接服务:用每个账户自己的登录凭证登录,再通过 API 把数据提供出来。
- 只读的投资者密码:每个账户开一个终端会话,导出交易历史和余额。
这几条路线在速度、成本、可靠性以及各家券商条款是否允许上都不一样,所以我会从第一天起就设计一个券商适配器接口。系统的其余部分只向适配器要账户、成交记录和持仓,永远不知道背后走的是哪条路线。账户用(服务器,登录号)来标识,因为同一个登录号在两家券商就是两个不同的人。
报价之前,我会要求看一下凭证的类型、它所属的服务器、覆盖哪些组别,以及它能不能写(余额操作、交易权限),还是只能读。两条官方路线的比较,见 MT5 Manager API 还是 Web API。
问题二:账户由谁来交易?
常见的答案有三种:每个投资者自己交易自己的账户,由一位管理人代为交易,或者从一个主账户跟单复制。在数据库里,这三种看起来差不多。在监管机构眼里却不一样:在很多地方,代他人操作资金或收取业绩费属于需要牌照的业务,在你所在的地方是不是,要问你的律师。
这个答案也决定了看板要展示什么。自己交易的人要看持仓、保证金水平和交易历史。托管账户或跟单账户的投资者,要看扣除费用后的业绩,以及一份对账单,能和券商出的那份核对上。IB 要看客户的交易量和佣金,不需要比这更私人的信息。
问题三:这个钱包在法律上和功能上到底是什么?
“钱包”这个词涵盖三种不同的产品。预付费用额度:投资者充值,平台费用从中扣除。储值:可以提现、也可以转给其他用户的钱。交易本金:直接为 MT5 账户入金的钱。
提现和用户之间的转账,可能让钱包变成受监管的储值产品,随之而来的牌照要求和客户资金保障义务因国家而异。设计开始之前,先拿到律师的书面答复,因为它会改变数据模型、后台,以及哪些支付服务商愿意接你这个客户。
三种钱包我都会建在同一个基础上:一本复式记账、只追加的账本,投资者账户和 IB 账户分开。余额由分录推导出来,从不原地修改。
每一类资金事件(充值、费用、佣金、提现)在代码里只有一条记账路径:测试只看一处,审计也只看一处。我没有在生产环境运行过记有客户余额的账本,所以文章开头的说明才要求做独立评审。具体模式见金融科技初创公司的复式记账账本。
问题四:佣金或业绩费的公式到底是什么?
“利润的 20%”这样一句话不是公式。写任何代码之前,下面每一项都要有书面答案:
- 费率是多少,是否因投资者、账户或 IB 层级而不同。
- 是否采用高水位线(high-water mark),也就是亏损补回来之后才再次收费。
- 交易账户上的入金和出金怎么处理。
- 只算已平仓盈亏,还是连浮动盈亏也算。未平仓的盈利仍然可能消失。
- 货币换算:用哪个汇率、取哪个时点的汇率,把账户货币换成钱包货币。
- 钱包余额为零时怎么办:允许负余额、记一笔欠款、部分扣费,还是暂停访问。
- 多级 IB 之间怎么分成,以及资格规则(最低交易量、活跃客户数、佣金追回)。
然后我会要三个书面算例,用真实的数字:一个普通的盈利月,一个既有入金又有出金的月份,以及一次先亏损后回本的情况。它们会成为第一批自动化测试。如果公司里两个人对同一个算例算出不同的答案,问题就在代价最低的时候被发现了。
// Amounts are integer minor units (cents) of the account currency.
type FeeInput = {
hwm: bigint; // high-water mark carried in from the last charged period
netDeposits: bigint; // balance deals in the period: deposits minus withdrawals
endEquity: bigint; // or end balance, if the agreement says closed P/L only
rateBps: bigint; // 2000n = 20%
};
type FeeResult = { fee: bigint; newHwm: bigint; gainAboveMark: bigint };
export function performanceFee(input: FeeInput): FeeResult {
const { hwm, netDeposits, endEquity, rateBps } = input;
if (rateBps < 0n || rateBps > 10_000n) throw new Error('rate out of range');
// A deposit is not profit, a withdrawal is not a loss: the mark moves with the cash.
const mark = hwm + netDeposits;
const gainAboveMark = endEquity - mark;
// At or below the mark: no fee. Losses never lower the mark.
if (gainAboveMark <= 0n) return { fee: 0n, newHwm: mark, gainAboveMark };
// bigint division truncates, so the fee rounds down. Agree that rule in writing.
const fee = (gainAboveMark * rateBps) / 10_000n;
// The fee is charged to the wallet, not taken from the trading account,
// so the account's equity is unchanged and becomes the new mark.
return { fee, newHwm: endEquity, gainAboveMark };
}
// Worked example (USD): carried mark 10,000.00, deposit 2,000.00,
// withdrawal 500.00, end equity 12,650.00, rate 20%.
// mark = 1_000_000 + (200_000 - 50_000) = 1_150_000
// gain = 1_265_000 - 1_150_000 = 115_000 (1,150.00)
// fee = 115_000 * 2000 / 10_000 = 23_000 (230.00)
// new mark = 1_265_000 (12,650.00)
const result = performanceFee({
hwm: 1_000_000n,
netDeposits: 150_000n,
endEquity: 1_265_000n,
rateBps: 2000n,
}); // { fee: 23_000n, newHwm: 1_265_000n, gainAboveMark: 115_000n }这个版本按出入金的金额平移水位线。有些协议则是在出金时按比例下调水位线,所以要查清楚你的合同写的是哪一种。
问题五:由哪个法律实体收款?
支付这一侧由四个事实决定:哪家公司收投资者的钱,在哪个国家,持有哪些牌照,以及有没有支付服务商书面认可过这个业务类别。对很多银行卡收单机构和支付网关来说,外汇和投资属于受限类别。如果服务商的风控团队拒绝开户,或者上线后把账户关掉,沙盒里跑通的对接什么也证明不了。
比较稳妥的上线顺序是:先做人工充值,由管理员确认后账本才记账;提现由人工打款,走双人复核(maker-checker),一个人准备付款,另一个人审批。支付网关、用户间转账和稳定币,等服务商和律师都点头之后再做。
牌照和客户资金方面的问题,归业务方和它的律师负责。我的职责是把系统设计好,让他们要求的控制措施(审批、限额、审计记录)不用重写就能满足。
我会搭建的架构
五个答案都有了之后,系统本身并不特别:一个模块化单体,只有 MT5 桥接服务单独拆出来。桥接服务之所以独立,是因为 Manager API 的 SDK 是 Windows 原生的,而且同一个连接上的调用必须串行化。我为自营交易公司做的对接也是这样拆分的:一个跑在 Windows 上的 .NET 服务,由主后端通过 HTTP 调用。
- MT5 适配器:每条券商接入路线一个,都在同一个接口后面。
- 同步任务:按相互重叠的时间窗口拉取成交记录,并给每一条分类:是交易(
DEAL_BUY、DEAL_SELL)还是余额操作(DEAL_BALANCE、DEAL_CREDIT);如果是交易,再看进出场方向(ENTRY_IN、ENTRY_OUT、ENTRY_INOUT、ENTRY_OUT_BY)。 - 成交库:放在你自己的数据库里,以(服务器,登录号,成交单号)为唯一键,重复拉取同一个窗口不会改变任何数据。
- 佣金引擎:只依据已入库的成交记录和当期规则做纯计算。
- 账本:资金只在这里变动。
- 投资者看板和 IB 看板,以及带双人复核审批的管理后台。
公式依赖这一步分类。已实现盈利记在平仓成交上(out、in-out、out-by),而入金和出金是以余额类成交的形式出现的。分类一旦出错,入金就会被算成利润。
对投资者看板来说,每隔几分钟同步一次就够了。逐 tick 的实时推送成本高得多,除了风控监控之外很少用得上。有一条来自实际项目的规则可以原样沿用:MT5 返回“没有数据”时,要当作失败处理,绝不能当作空结果。把账户标记为数据过期,显示最后一次成功读取的时间,并通知工作人员。
月末佣金结算与 IB 关系树
佣金结算是系统里风险最高的任务:它一次性给所有账户记账。我会让它具备四个特性:
- 每个(账户,结算周期)一个唯一键,由数据库强制保证,重跑也不会重复记账。
- 任务可以续跑:进度按账户记录,中途崩溃的任务会从停下的地方继续。
- 先出预览:工作人员审核并批准之后,才有任何内容写入账本。
- 给 IB 的对账单:列出每个客户账户、计算基数、费率和结果。
IB 的层级关系要用闭包表或物化路径来存,让一个客户的所有上级都能低成本地查出来,因为每一笔佣金都要沿着树向上走。还要按结算周期保存快照,因为 IB 会调动、会离开、会被重新分配。三月的佣金必须按三月的关系树来算,哪怕有人在六月重跑。
一页纸看完五个问题
| 问题 | 为什么重要 | 对范围或成本的影响 |
|---|---|---|
| 1. 有什么 MT5 接入权限,对应哪些券商 | 一个登录账号只能读一家券商的服务器,别的都读不到 | 一个适配器还是多个,第三方桥接服务的费用,只读还是可读可写 |
| 2. 账户由谁交易 | 代客交易和跟单交易可能属于需要牌照的业务 | 需要哪些看板和对账单,以及业绩费到底适不适用 |
| 3. 钱包是什么 | 提现和转账可能让它成为受监管的储值产品 | 费用额度只需要一本小账本。储值则要加上客户资金保障、限额和后台控制措施 |
| 4. 费用和佣金的公式 | 含糊之处会变成与投资者和 IB 之间的纠纷 | 佣金引擎、测试和对账单的规模 |
| 5. 由哪个实体收款 | 对受限类别,服务商会拒绝开户或关闭账户 | 上线时用人工充值,还是对接支付网关并走完它的审批流程 |
稳定币入金,两句话说完
先接一家能分配充值地址并处理链上确认的支付服务商,把它的回调当作普通的支付 webhook 来处理。自己在链上监测入金是后面的事,工作量更大,还要有单独的对账,而且这一块我没有在生产环境运行过。
一次付费技术调研应该交付什么
我不会只凭一份功能清单就给整个项目报价。先做一次付费的技术调研或概念验证,它应该交付:
- 通过适配器接通一家券商,用的是你实际拥有的凭证。
- 真实的成交记录和账户数据进入成交库,已完成分类,并且能检测出过期数据。
- 公式已经实现,并通过那三个算例。
- 一份调研报告:这个接入权限能做什么,数据反映出什么,以及还没有答案的法律问题和服务商问题。
- 一份里程碑计划,范围划分到可以分段报价。
这类工作我承接:可以只做 MT5 对接,也可以以金融科技技术合伙人的身份负责更大范围的构建。如果五个问题里你今天能回答两三个,把你手上的信息发给我,我会告诉你我会先核实什么。
- MetaTrader 5
- 投资者门户
- IB 佣金
- 账本
- 架构