刷爆全网的 AI 大模型交易?可以玩,玩破产的那种

280 阅读9分钟

AI 链上交易

昨天晚上,一张图刷爆了内外网。

image.png

上周六(10 月 18 日),美国人工智能研究实验室 nof1.ai 在其"阿尔法竞技场"平台上举办了一场活动:给六个顶级模型一万美元的真金白银,让它们下场交易,而且并非模拟盘,真金白银地交易。

注意,不是模拟盘,而是真枪实战。

规则也很简单,所有的模型,都以相同提示词与输入数据条件下,交易加密货币的永续合约。目的都是"将风险调整后的收益最大化",每个模型必须自行确定仓位、择时交易、管理风险和产生收益。

经过近 60 小时的激战后,DeepSeek 的持仓总市值接近 1.4 万美元,收益率约 40%,最高时一度接近 1.5 万美元,是当前表现最好的模型,马斯克的 Grok 紧随其后,垫底的是 OpenAI 的 GPT 和 Google 的 Gemini,收益为负。

看到这里,不少长期和 AI 打交道的同学开始后台留言给我,问我看法。

就差把 "三叶,我也想搞 AI 炒股,投多少钱合适" 写在脑袋上了 🤣

且慢,我明白 60 小时 40% 收益率是很有吸引力,但那大概率不可复刻。

第一个很简单的问题:“相同模型的,再来一次的结果还会是一样吗?

BTC 的行情数据是公开的,模型也是开源的,你完全可以拿相同版本的 DeepSeek 在相同时间点,喂相同的提示词来做回测,然后又是相同的结束时间,大概率,收益率结果不会相同。

这样的回测是有意义且科学的。

因为六个模型,六万美元起始资金,60 个小时的实盘交易,并不会真正地左右到 BTC 的行情,所以拿这 60 小时的数据来回测,并不会引入"时间函数",结果是可信的。

也就是,但凡结果有变化,说明当时的实盘结果就是有"随机性"的。

如果第一个问题的解答没听懂,没关系,第二个很简单的问题:更好的模型,就一定能拿到更好的结果吗?

例如用 DeepSeek-V3.2 来实盘交易,就一定比 DeepSeek-V3.1 的 40% 收益率更好吗?

这个实验就是有成本的了,不像回测那样,人人都可以动手验证。

但我估计大家心里都会有答案,更好的模型,大概率不一定会有更好的结果,榜单的 GPT 和 Gemini 可都是世界前列的选手,但收益率却是不如大盘。

综上所述,上面的所谓实盘结果,没有太大意义,只有 60 小时的博弈,不能说明任何问题。

我甚至都能想象到模型们决策时的推理过程:

DS、Grok:比特币市值最大,流动性最好,买它!

GPT:链上波动大,运行多空策略!

Gemini:发挥硅基生物优势,做超短线!

上面的猜想,只是图一乐,我没细看模型的对话内容,但意思是那个意思。

目前的结果,更多就在首轮对话,就已经决定了,DS 和 Grok 可能只是在某次(随机)决策中选中了好个股,然后叠加这 60 小时的好行情,做出来了好成绩。

但这些获得高收益率的模型,不代表交易水平有多好,至少目前不能这样论证。

如果你还是不信,你可以投钱试试。

...

上面那一段,是我上午写的,但忘记定时发送了,下午爬起来后,抱着能劝一个是一个的心态,看了一眼最新结果。

截止发文前,Grok 已经几乎回吐完了此前收益,DeepSeek 收益率也从 40% 下降到了 11%

image.png

无它,因为行情不好了。

image.png

...

回归主题。

来道和「字节跳动」相关的算法题。

题目描述

平台:LeetCode

题号:886

给定一组 n 人(编号为 1, 2, ..., n), 我们想把每个人分进任意大小的两组。

每个人都可能不喜欢其他人,那么他们不应该属于同一组。

给定整数 n 和数组 dislikes ,其中 dislikes[i]=[ai,bi]dislikes[i] = [a_i, b_i] ,表示不允许将编号为 aia_i 和  bib_i 的人归入同一组。

当可以用这种方法将所有人分进两组时,返回 true;否则返回 false

示例 1:

输入:n = 4, dislikes = [[1,2],[1,3],[2,4]]

输出:true

解释:group1 [1,4], group2 [2,3]

示例 2:

输入:n = 3, dislikes = [[1,2],[1,3],[2,3]]

输出:false

示例 3:

输入:n = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]

输出:false

提示:

  • 1<=n<=20001 <= n <= 2000
  • 0<=dislikes.length<=1040 <= dislikes.length <= 10^4
  • dislikes[i].length=2dislikes[i].length = 2
  • 1<=dislikes[i][j]<=n1 <= dislikes[i][j] <= n
  • ai <bia_i < b_i
  • dislikes 中每一组都不同

染色法

无论是从题目描述和对点边的描述,这都是一道「染色法判定二分图」的模板题。

为了方便,我们令 dislikesds,将其长度记为 mm

题目要求我们将 nn 个点划分到两个集合中,同时我们将每个 ds[i]ds[i] 看做无向边的话,可知集合内部无边,即所有的边必然横跨两个集合之间。

使用 ds[i]ds[i] 进行建图,并将两个将要划分出的两个集合分别记为 AB,我们可以采用「染色」的方式,尝试将所有点进行划分。

构建一个与点数相等的数组 color,我们人为规定划分到集合 A 的点满足 color[i]=1color[i] = 1,划分到集合 B 的点满足 color[i]=2color[i] = 2,起始有 color[i]=0color[i] = 0,代表该点尚未被划分。

随后我们可以实现 DFS 函数为 boolean dfs(int u, int cur) 含义为尝试将点 ucur 色。根据定义可知,我们除了需要 color[u] = cur 以外,还需要遍历点 u 的所有出边(处理其邻点,将其划分到另一集合上),若在处理过程中发生冲突,则返回 false,若能顺利染色则返回 true

由于我们固定了颜色编号为 12,因此 cur 的对立色可统一为 3 - cur

最终,我们根据能否给所有点染色成功来决定答案。

Java 代码:

class Solution {
    int N = 2010, M = 2 * 10010;
    int[] he = new int[N], e = new int[M], ne = new int[M], color = new int[N];
    int idx;
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx++;
    }
    boolean dfs(int u, int cur) {
        color[u] = cur;
        for (int i = he[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (color[j] == cur) return false;
            if (color[j] == 0 && !dfs(j, 3 - cur)) return false;
        }
        return true;
    }
    public boolean possibleBipartition(int n, int[][] ds) {
        Arrays.fill(he, -1);
        for (int[] info : ds) {
            int a = info[0], b = info[1];
            add(a, b); add(b, a);
        }
        for (int i = 1; i <= n; i++) {
            if (color[i] != 0) continue;
            if (!dfs(i, 1)) return false;
        }
        return true;
    }
}

C++ 代码:

class Solution {
public:
    int he[2010], e[2 * 10010], ne[2 * 10010], color[2010], idx = 0;
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx++;
    }
    bool dfs(int u, int cur) {
        color[u] = cur;
        for (int i = he[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (color[j] == cur) return false;
            if (color[j] == 0 && !dfs(j, 3 - cur)) return false;
        }
        return true;
    }
    bool possibleBipartition(int n, vector<vector<int>>& ds) {
        fill(he, he + n + 10, -1);
        for (const auto& info : ds) {
            int a = info[0], b = info[1];
            add(a, b); add(b, a);
        }
        for (int i = 1; i <= n; i++) {
            if (color[i] != 0) continue;
            if (!dfs(i, 1)) return false;
        }
        return true;
    }
};

Python 代码:

class Solution:
    def possibleBipartition(self, n: int, ds: List[List[int]]) -> bool:
        N, M = 2010, 20010
        he, e, ne, color = [-1] * N, [0] * M, [0] * M, [0] * N
        idx = 0
        def add(a, b):
            nonlocal idx
            e[idx], ne[idx], he[a] = b, he[a], idx
            idx += 1
        def dfs(u, cur):
            color[u] = cur
            i = he[u]
            while i != -1:
                j = e[i]
                if color[j] == cur:
                    return False
                if color[j] == 0 and not dfs(j, 3 - cur):
                    return False
                i = ne[i]
            return True
        for info in ds:
            a, b = info[0], info[1]
            add(a, b)
            add(b, a)
        for i in range(1, n + 1):
            if color[i] != 0:
                continue
            if not dfs(i, 1):
                return False
        return True

TypeScript 代码:

function possibleBipartition(n: number, ds: number[][]): boolean {
    const N = 2010, M = 2 * 10010
    const he = new Array<number>(N).fill(-1), e = new Array<number>(M).fill(0), ne = new Array<number>(M).fill(0), color = new Array<number>(N).fill(0)
    let idx = 0
    function add(a: number, b: number): void {
        e[idx] = b
        ne[idx] = he[a]
        he[a] = idx++
    }
    function dfs(u: number, cur: number): boolean {
        color[u] = cur
        for (let i = he[u]; i != -1; i = ne[i]) {
            const j = e[i];
            if (color[j] == cur) return false
            if (color[j] == 0 && !dfs(j, 3 - cur)) return false
        }
        return true
    }
    for (const info of ds) {
        const a = info[0], b = info[1]
        add(a, b); add(b, a)
    }
    for (let i = 1; i <= n; i++) {
        if (color[i] != 0) continue
        if (!dfs(i, 1)) return false
    }
    return true
}
  • 时间复杂度:O(n+m)O(n + m)
  • 空间复杂度:O(n+m)O(n + m)

反向点 + 并查集

我们知道对于 ds[i]=(a,b)ds[i] = (a, b) 而言,点 a 和点 b 必然位于不同的集合中,同时由于只有两个候选集合,因此这样的关系具有推断性:即对于 (a,b)(a, b)(b,c)(b, c) 可知 ac 位于同一集合。

因此,我们可以对于每个点 x 而言,建议一个反向点 x + n:若点 x 位于集合 A 则其反向点 x + n 位于集合 B,反之同理。

基于此,我们可以使用「并查集」维护所有点的连通性:边维护变检查每个 ds[i]ds[i] 的联通关系,若 ds[i]=(a,b)ds[i] = (a, b) 联通,必然是其反向点联通所导致,必然是此前的其他 ds[j]ds[j] 导致的关系冲突,必然不能顺利划分成两个集合,返回 false,否则返回 true

Java 代码:

class Solution {
    int[] p = new int[4010];
    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }
    void union(int a, int b) {
        p[find(a)] = p[find(b)];
    }
    boolean query(int a, int b) {
        return find(a) == find(b);
    }
    public boolean possibleBipartition(int n, int[][] ds) {
        for (int i = 1; i <= 2 * n; i++) p[i] = i;
        for (int[] info : ds) {
            int a = info[0], b = info[1];
            if (query(a, b)) return false;
            union(a, b + n); union(b, a + n);
        }
        return true;
    }
}

C++ 代码:

class Solution {
public:
    vector<int> p;
    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }
    void unionp(int a, int b) {
        p[find(a)] = p[find(b)];
    }
    bool query(int a, int b) {
        return find(a) == find(b);
    }
    bool possibleBipartition(int n, vector<vector<int>>& ds) {
        p.resize(2 * n + 1);
        for (int i = 1; i <= 2 * n; ++i) p[i] = i;
        for (const auto& info : ds) {
            int a = info[0], b = info[1];
            if (query(a, b)) return false;
            unionp(a, b + n);
            unionp(b, a + n);
        }
        return true;
    }
};

Python 代码:

class Solution:
    def possibleBipartition(self, n: int, ds: List[List[int]]) -> bool:
        p = [i for i in range(0, 2 * n + 10)]
        def find(x):
            if p[x] != x:
                p[x] = find(p[x])
            return p[x]
        def union(a, b):
            p[find(a)] = p[find(b)]
        def query(a, b):
            return find(a) == find(b)
        for info in ds:
            a, b = info[0], info[1]
            if query(a, b):
                return False
            else:
                union(a, b + n)
                union(b, a + n)
        return True

TypeScript 代码:

function possibleBipartition(n: number, ds: number[][]): boolean {
    const p = new Array<number>(4010).fill(0)
    function find(x: number): number {
        if (p[x] != x) p[x] = find(p[x])
        return p[x]
    }
    function union(a: number, b: number): void {
        p[find(a)] = p[find(b)]
    }
    function query(a: number, b: number): boolean {
        return find(a) == find(b)
    }
    for (let i = 1; i <= 2 * n; i++) p[i] = i
    for (const info of ds) {
        const a = info[0], b = info[1]
        if (query(a, b)) return false
        union(a, b + n); union(b, a + n)
    }
    return true
}
  • 时间复杂度:O(n+m)O(n + m)
  • 空间复杂度:O(n)O(n)