B.F.M. UNITY
6.0.0
6.0.0
  • 🏁B.F.M. UNITY & U.R.S. & R.V.C.(On war with ShenNanPeng SEQUOIA HongShan & DaHongFei NEO Antshares)
  • 🩸B.F.M. R.V.C. 执行令
  • 沈南鹏,姚期智,去所里办点事吧
    • 沈南鹏,姚期智,去所里办点事吧
  • 🌟BF​M UNITY : A Brief Fantasy of Multiverse
  • 🫀古灵阁米达斯银行,请对洋人使用炼金术吧
  • B.F.M. R.V.C. | 位面简梦复仇做空风投
    • 中国加密项目名录
  • 中国金融机构名录
  • 红杉资本中国生态
  • 红杉资本全球生态
  • Antbot Antshares NEO 生态
  • 币安投资
  • 做市商名单
  • 华尔街RWA
  • 隐私代币
  • 人工智障
  • NFT
  • 跨链桥
  • 隐私代币
  • launchpad
  • BFM Lite | 位面简梦轻量版
    • 🌱新人必看(🔥)
    • 驾驶舱 (运行) (🔥)
      • 私钥云保管 🔑 ☁️
    • BFM Navigation
    • Main Bridge (Navigator)
  • BFM URS|位面简梦联合体储备系统(区块链加密货币宏观调控与货币政策机构)
    • 《位面简梦联合体(BFM-UNITY)储备系统(简称,位面简梦联储,位面联储,BFM-URS)法》
    • 😇历任行长(嵌套螺旋·符文占星·死灵炼金·原语谕示)
    • 🌟新资料片:寒武纪大过滤器与奥陶纪跃迁引擎启动!Smart Beta Neutral ——奎扎尔·科亚特尔计划
    • 战略资产配置——卓尔金历 🕙
      • 卓尔金升级1——拟合函数
      • 卓尔金升级2——误差分析
      • 卓尔金升级3——智能实验
      • 卓尔金升级4——图灵完备
      • 卓尔金升级5——动态图表
      • 卓尔金升级6——分类网络
      • 卓尔金升级7——引力透镜
      • 卓尔金升级8——货币政策
      • 卓尔金升级9——杠杆控制
      • 卓尔金升级A1——市占幂修
      • 卓尔金升级A2——参数读取
      • 卓尔金升级A3——止损熔断
      • 卓尔金升级A4——最大回撤
      • 卓尔金升级A5——波动率止损
      • 卓尔金升级A6——安全带
      • 卓尔金升级A7——在线图表
      • 卓尔金升级A8——商业化
      • 卓尔金升级A9——下拉菜单
      • 卓尔金升级A10——xlwings与Python连接
      • 卓尔金升级A11——KDE核密度估计
      • 卓尔金升级A12——杠杆风险控制,凯利准则
      • Plotly-制作数据仪表盘
      • 如何获取数字货币数据
      • 卓尔金升级A13——无监督学习优化
      • 卓尔金历法旧版页面
    • 战术资产配置——哈布历⌚️
      • 哈布历法1层次
      • 哈布历法2层次
      • 哈布历法3——仓位单元,量子调仓
      • 已废弃
    • 资产配置 🕙 ⌚️(🔥)
    • 时间校准研究
    • 如何碾碎中美政府国际金融部门
    • 如何碾碎中国人民银行
    • 如何碾碎中国三大政策性银行
    • 如何碾碎中国四大国有银行
    • 如何碾碎美联储
  • BFM Unity | 位面简梦联合体
    • 陈列室 (历程)
      • 虚拟资产编号名录
    • 新人指导
      • 【管理】宪章说明
      • 【选择】学区房 or 比特币
    • 【运营】加入组建
      • 群公告备份
    • 【组织】团队构成
  • BFM DA | 位面简梦数产
    • BFM Unity Reserve System
    • BFM Foundation
      • 比坊梦全球互联网 基金
      • 若尘的基金
    • BFM Trading Strategy♟
    • 影银行 (枢纽)
      • 自营业务
      • 个人业务
      • 企业业务
      • 创世业务
    • 会客厅 (赞助) 💰 & 资产公示
    • 财务
  • 🦣BFM Eco. | 位面简梦区块生态
    • BFM Eco. 🛰️
    • 链上数据分析
    • Cross-Chain Bridge
    • ETH - MainNet
      • Layer-2
        • MATIC - Polygon
        • Arbitrum
        • OP - Optimism
      • SHIB - Shiba Inu
    • BNB - BSC
    • AVAX - Avalanche
    • SOL - Solana
    • 其他公链
      • ADA - Cardano
      • ATOM - Cosmos
        • ☠️LUNA - Terra(已亡)
      • FTM - Fantom
      • Flow
      • Near
      • IPFS/FIL
    • BFM Meta
      • BFM DeFi 🦄️👻
      • BFM NFT🃏🧩
      • BFM Meme 🐶 💩
      • BFM GameFi 🎮👾
  • BFM DS|位面简梦数据支撑
    • Page 5
  • BFM OL|位面简梦障碍解除
    • SS/V2Ray 科学上网 ✈️
    • 华谷套件(Google Play商店)
    • 跨境收付
    • 离岸实体
    • Page 3
  • BFM BM|位面简梦区块基础
    • “在座的各位”总集篇
      • 讲师阵容
      • 介绍在座的各位
      • 问候在座的各位
      • 评价在座的各位
    • 《精通比特币》《精通以太坊》
    • 区块链安全
    • 区块链共识算法
    • 区块链分类与层次
    • 区块链要素理论
    • 区块链评级
    • 私钥与BIP44
    • 钱包
    • 交易所
    • 实体挖矿
    • 质押挖矿
    • 云算力挖矿
    • 桥接性钱包&加密银行卡
    • 礼品卡&场外交易
  • BFM Trad. | 位面简梦传统金融
    • FOReign EXchange
    • Cloud-POS,CNP 💳
    • Stock Investment
      • 选股
    • Fund Investment
      • 金融
      • 地产
      • 白酒
      • 医药
      • 互联网
      • 新能源
    • Gold investment
    • 国债逆回购
    • 可转债打新
    • Offshore Finance 🌍
    • Offshore Entity
    • International Phone Number
    • International Bank Card
    • 桥接性数字钱包
    • 礼品卡与场外交易
    • 草稿
  • BFM R-Ins. | 位面简梦科学研究院
    • 数学研究部
    • 算法科学部
      • 【算法】算法描述
        • 【研究】参考文献
        • BFM 维基百科翻译
        • 陵墓
    • 组织架构部
    • 项目流程部
      • 【软件】行动手册 🚩
      • 【软件】其他代码
      • 【软件】Graph algorithms and currency arbitrage
      • 【创新】未来发展
      • 【创新】DeFi - AMM
    • 项目架构部
      • 【架构】架构设计
        • 【规划】发展路线
        • 【规划】AMM下潜
        • 弯路
    • 量子科学部
      • LV5-研究院 (量子) ☢️
    • 数据智能部
      • Page
      • LV6-星魔方 (分类) 🎲
        • 数据分析-精炼
        • 数据分析-实验
        • 人工智能-实验
  • BFM D-Ins. | 位面简梦工程设计院
    • API接口
      • 币安API实验室🚩
    • 高并发
      • 新版本高并发实验室🚩
      • 旧版本高并发实验室
    • 开源项目部
      • 总览
      • Freqtrade 领域级重点实验室
      • HummingBot领域级重点实验室🚩
      • CCXT 领域级重点实验室🚩
        • CCXT文档
        • CCXT手册
      • AIOQuant 实验室
      • Peregrine 领域级重点实验室🚩
      • btrader实验室
      • js实验室1
      • 实验室2
      • py重点实验室
      • 以太坊部署实验室
    • 衍生产品部
    • 量化交易部
  • BFM Univ. | 位面简梦大学
    • 素白 · 密斯卡托尼克大学 🏫
    • Excel 教室
    • Power BI 教室
    • SPSS 教室
    • Python少儿编程教室 👩‍🏫
      • Python 办公自动化 OA
      • Python 金融 Finance
      • Python Project
    • 开发环境搭建教室 👨‍🏫 🚩
    • 文献与数据
    • 网络安全
    • MIS 系统开发
    • WEB 全栈开发
    • 企业战略分析
    • 基础财商教育
    • C#教室
    • 金融考试
    • 计算机考试
    • 发卡卡密交易平台
  • BFM LIB.|位面简梦图书馆
    • Page 1
    • Page 2
  • BFM Cult. | 位面简梦文化
    • 组织文化宣传
    • 设计
    • 组织文化
      • IT超度指南
      • 动漫
      • 漫画
      • 游戏
      • 01城密咒
      • 心理学
    • 风水玄学儒释道瑜伽占卜塔罗吸引力法则灵性修行
    • 【传媒】引起兴趣
  • 风控与合规
    • LICENCE:GNU GPL v3.0
  • 工具
    • AIGC
    • 文章论文生成器
    • 绘画生成器
    • AI 导航网站
    • 发现网站
    • B站视频下载 🎬 ⏬
  • 链接
    • 看板
    • 投资方法论
    • Github托管地址
    • 旧群文件
  • 位面简梦回收站
    • 三角套利程序众筹(中止)
  • 位面简梦后勤部
    • 餐厅
    • 药店
    • 服饰店
    • 数码店
    • 钱包店
    • 家具店
  • BFM AI|位面简梦智能
    • Page 4
Powered by GitBook
On this page
  • Graph algorithms and currency arbitrage, part 1
  • Graphs
  • Graphs for currency arbitrage
  • Conclusion
  • Graph algorithms and currency arbitrage, part 2
  • Raw data
  • Preparing the data
  • Putting the data into a graph
  • Bellman-Ford
  • Tying it all together
  • Why wouldn’t this work?
  • Conclusion
  1. BFM R-Ins. | 位面简梦科学研究院
  2. 项目流程部

【软件】Graph algorithms and currency arbitrage

Previous【软件】其他代码Next【创新】未来发展

Graph algorithms and currency arbitrage, part 1

Arbitrage is the holy grail for traders and the bedrock of financial academia. Let’s say you are in an open marketplace with Alice selling oranges for $1 each and Bob buying them for $2. As a cunning trader, you realise you can buy an orange from Alice and immediately sell it to Bob for $1 of “risk-free” profit. However, as you keep reaping this $1 profit by buying up Alice’s oranges, she raises her prices. Eventually, Alice would be charging $2 per orange and the arbitrage would be shut. In other words, acting on arbitrage opportunities causes the market to become more efficient, with everything as close to its fair price as possible. Because of this, the only way to profit from true arbitrage is to find and execute opportunities faster than everybody else.

Currency exchange is a natural place to search for arbitrage because there are many different pairs that we can trade. For example, assume we are able to convert GBP to USD, then USD to EUR, then EUR to AUD for an effective rate of 1:2 GBP:AUD. If we were then able to directly swap AUD back to GBP in the ratio 2:1.1, this would be an arbitrage opportunity because we end up with more GBP than we started with while taking zero risk.

Graphs

It is useful to formulate real-world problems in terms of graphs because we have about three centuries worth of relevant theory – they were first investigated by Euler in 1736. In particular, there exist many efficient algorithms related to finding the shortest path along a graph, which have widespread applications e.g in mapping.

This is actually the main reason why we are choosing to use Bellman-Ford over another shortest path algorithm like Dijkstra, despite the latter being asymptotically more efficient. Bellman-Ford is able to detect if there is a negative-weight cycle and as will be seen shortly, this will be the key to detecting arbitrage opportunities.

Graphs for currency arbitrage

To find the exchange rate between two currencies that do not share an edge, we simply find a path between the two currency vertices and walk along it, multiplying by each successive edge weight (exchange rate). If there are multiple possible paths, choose the path that has the greatest weight product, which corresponds to the most favourable rate.

What would happen if we start with 1 GBP and convert it along GBP →→ USD →→ AUD →→ GBP? Well, multiplying out the edges we see that our initial 1 GBP becomes 1.27×1.43×0.55≈0.9991.27×1.43×0.55≈0.999 GBP. That is to say, we lose a tenth of a penny on the round trip. However, if this number were greater than 1, it would mean that by following this path we end up with more GBP than we started with – this is textbook arbitrage.

So the problem of finding arbitrage can thus be formally posed as follows: find a cycle in the graph such that multiplying all the edge weights along that cycle results in a value greater than 1. In fact we have already described an algorithm that can find such a path – the problem is equivalent to finding a negative-weight cycle, provided we do some preprocessing on the edges.

Firstly, we note that Bellman-Ford computes the path weight by adding the individual edge weights. To make this work for exchange rates, which are multiplicative, an elegant fix is to first take the logs of all the edge weights. Thus when we sum edge weights along a path we are actually multiplying exchange rates – we can recover the multiplied quantity by exponentiating the sum. Secondly, Bellman-Ford attempts to find minimum weight paths and negative edge cycles, whereas our arbitrage problem is about maximising the amount of currency received. Thus as a simple modification, we must also make our log weights negative.

With these two tricks in hand, we are able to apply Bellman-Ford. The minimal weight between two currency vertices corresponds to the optimal exchange rate, the value of which can be found by by exponentiating the negative sum of weights along the path. As a corollary, we have the wonderful insight that: a negative-weight cycle on the negative-log graph corresponds to an arbitrage opportunity.

Conclusion

Graph algorithms and currency arbitrage, part 2

Rather than using fiat currencies as presented in the previous post, we will examine a market of cryptocurrencies because it is much easier to acquire crypto order book data. We’ll narrow down the problem further by making two more simplifications. Firstly, we will focus on arbitrage within a single exchange. That is, we’ll look to see if there are pathways between different coins on an exchange which leave us with more of a coin than we started with. Secondly, we will only be considering a single snapshot of data from the exchange. Obviously markets are highly dynamic, with thousands of new bids and asks coming in each second. A proper arbitrage system needs to constantly be scanning for opportunities, but that’s out of the scope of this post.

With all this in mind, the overall implementation strategy was as follows:

  1. For a given exchange, acquire the list of pairs that will form the vertices.

  2. For each of these pairs, download a snapshot of the bid/ask.

  3. Process these values accordingly, assigning them to directed edges on the graph.

  4. Using Bellman-Ford, find and return negative-weight cycles if they exist.

  5. Calculate the arbitrage that these negative-weight cycles correspond to.

Raw data

As mentioned previously, we will only look at data from Binance. I chose Binance not because it has a large selection of altcoins, but because most altcoins can trade directly with multiple pairs (e.g BTC, ETH, USDT, BNB). Some exchanges have many altcoins but you can only buy them with BTC – this is not well suited for arbitrage.

Firstly, we need to find out which pairs Binance offers. This is done with a simple call (AUTH is your API key string):

import requests
import json 

def top_exchange_pairs():
    url = (
        "https://min-api.cryptocompare.com/data/v3/all/" + 
        "exchanges?topTier=true&api_key=" + AUTH
    )
    r = requests.get(url)
    with open("pairs_list.json", "w") as f:
        json.dump(r.json(), f)

This is an excerpt from the resulting JSON file – for each exchange, the pairs field lists all other coins that the key coin can be traded with:

"Data":{  
   "Binance":{  
      "isActive":true,
      "isTopTier":true,
      "pairs":{  
         "ETH":["PAX", "TUSD", "USDT", "USDC", "BTC"],
         "ONGAS":["BTC", "BNB", "USDT"],
         "PHX":["ETH","BNB","BTC"]
      }
   },
   "Coinbase":{  
      "isActive":true,
      "isTopTier":true,
      "pairs":{  
         "ETH":["DAI", "USD", "USDC", "EUR", "GBP", "BTC"],
         "BCH":["BTC", "GBP", "EUR", "USD"]
      }
   }
}

I then filtered out coins with fewer than three tradable pairs. These coins are unlikely to participate in arbitrage – we would rather have a graph that is more connected.

def binance_connected_pairs():
    with open("pairs_list.json", "r") as f:
        data = json.load(f)
    pairs = data["Data"]["Binance"]["pairs"]
    return {k: v for k, v in pairs.items() if len(v) > 3}

We are now ready to download a snapshot of the available exchange rates for each of these coins.

import os
import tqdm  # progress bar

def download_snapshot(pair_dict, outfolder):
    if not os.path.exists(outfolder):
        os.makedirs(outfolder)

    # Download data and write to files
    for p1, p2s in tqdm(pair_dict.items()):
        url = (
            "https://min-api.cryptocompare.com/data/" 
            + f"ob/l1/top?fsyms={p1}&tsyms={','.join(p2s)}"
            + "&e=Binance&api_key=" + AUTH
        )
        r = requests.get(url)
        with open(f"{outfolder}/{p1}_pairs_snapshot.json", "w") as f:
            json.dump(r.json(), f)

We can then run all of the above functions to produce a directory full of the exchange rate data for the listed pairs.

top_exchange_pairs()
connected = binance_connected_pairs()
download_snapshot(connected, "binance_data")
"EOS": {
    "BNB": {
        "BID": ".2073",
        "ASK": ".2077"
    },
    "BTC": {
        "BID": ".0007632",
        "ASK": ".0007633"
    },
    "ETH": {
        "BID": ".02594",
        "ASK": ".025964"
    },
    "USDT": {
        "BID": "7.0441",
        "ASK": "7.046"
    },
    "PAX": {
        "BID": "7.0535",
        "ASK": "7.07"
    }
}

This excerpt reveals something that we glossed over completely in the previous post. As anyone who has tried to exchange currency on holiday will know, there are actually two exchange rates for a given currency pair depending on whether you are buying or selling the currency. In trading, these two prices are called the bid (the current highest price someone will buy for) and the ask (the current lowest price someone will sell for). As it happens, this is very easy to deal with in the context of graphs.

Preparing the data

Having downloaded the raw data, we must now prepare it so that it can be put into a graph. This effectively means parsing it from the raw JSON and putting it into a pandas dataframe. We will arrange it in the dataframe such that it constitutes an adjacency matrix:

  • Column ETH row BTC is the bid:

    • i.e someone will pay x BTC to buy my 1 ETH

    • this is then the weight of the ETH →→ BTC edge.

  • Column BTC row ETH is the ask:

    • i.e I have to pay y BTC to buy someone’s 1 ETH

    • the reciprocal of this is the weight of the BTC →→ ETH edge.

I chose this particular row-column scheme because it results in intuitive indexing: df.X.Y is the amount of Y gained by selling 1 unit of X, and df.A.B * df.B.C * df.C.D is the total amount of D gained by trading 1 unit of A when trading via A→B→C→DA→B→C→D.

The column headers will be the same as the row headers, consisting of all the coins we are considering. The function that creates the adjacency matrix is shown here:

def create_adj_matrix(pair_dict, folder, outfile="snapshot.csv"):
    # Union of 'from' and 'to' pairs
    flatten = lambda l: [item for sublist in l for item in sublist]
    keys, vals = pair_dict.items()
    all_pairs = list(set(keys).union(flatten(values)))

    # Create empty df
    df = pd.DataFrame(columns=all_pairs, index=all_pairs)

    for p1 in pair_dict.keys():
        with open(f"{folder}/{p1}_pairs_snapshot.json", "r") as f:
            res = json.load(f)
        quotes = res["Data"]["RAW"][p1]
        for p2 in quotes:
            try:
                df[p1][p2] = float(quotes[p2]["BID"])
                df[p2][p1] = 1 / float(quotes[p2]["ASK"])
            except KeyError:
                print(f"Error for {p1}/{p2}")
                continue
    df.to_csv(outfile)

Putting the data into a graph

In particular, we will be using nx.DiGraph, which is just a (weighted) directed graph. I was initially concerned that it’d be difficult to get the data in: python libraries often adopt their own weird conventions and you have to modify your data so that is in the correct format. This was not really the case with NetworkX, it turns out that we already did most of the hard work when we put the data into our pandas adjacency matrix.

g = nx.DiGraph(-np.log(df).fillna(0).T)

Bellman-Ford

To implement Bellman-Ford, we make use of the funky defaultdict data structure. As the name suggests, it works exactly like a python dict, except that if you query a key that is not present you get a certain default value back. The first part of our implementation is quite standard, as we are just doing the n−1n−1 edge-relaxations where n is the number of vertices.

But because the ‘classic’ Bellman-Ford does not actually return negative-weight cycles, the second part of our implementation is a bit more complicated. The key idea is that if after n−1n−1 relaxations, there is an edge that can be relaxed further then that edge must be on a negative weight cycle. So to find this cycle we walk back along the predecessors until a cycle is detected, then return the cyclic portion of that walk. In order to prevent subsequent redundancy, we mark these vertices as ‘seen’ via another defaultdict. This procedure adds a linear cost to Bellman-Ford since we have to iterate over all the edges, but the asymptotic complexity overall remains O(VE)O(VE).

def bellman_ford_return_cycle(g, s):
    n = len(g.nodes())
    d = defaultdict(lambda: math.inf)  # distances dict
    p = defaultdict(lambda: -1)  # predecessor dict
    d[s] = 0

    for _ in range(n - 1):
        for u, v in g.edges():
            # Bellman-Ford relaxation
            weight = g[u][v]["weight"]
            if d[u] + weight < d[v]:
                d[v] = d[u] + weight
                p[v] = u  # update pred

        # Find cycles if they exist
        all_cycles = []
        seen = defaultdict(lambda: False)

        for u, v in g.edges():
            if seen[v]:
                continue
            # If we can relax further there must be a neg-weight cycle
            weight = g[u][v]["weight"]
            if d[u] + weight < d[v]:
                cycle = []
                x = v
                while True:
                    # Walk back along preds until a cycle is found
                    seen[x] = True
                    cycle.append(x)
                    x = p[x]
                    if x == v or x in cycle:
                        break
                # Slice to get the cyclic portion
                idx = cycle.index(x)
                cycle.append(x)
                all_cycles.append(cycle[idx:][::-1])
        return all_cycles

As a reminder, this function returns all negative-weight cycles reachable from a given source vertex (returning the empty list if there are none). To find all negative-weight cycles, we can simply call the above procedure on every vertex then eliminate duplicates.

def all_negative_cycles(g):
    all_paths = []
    for v in g.nodes():
        all_paths.append(bellman_ford_negative_cycles(g, v))
    flattened = [item for sublist in all_paths for item in sublist]
    return [list(i) for i in set(tuple(j) for j in flattened)]

Tying it all together

The last thing we need is a function that calculates the value of an arbitrage opportunity given a negative-weight cycle on a graph. This is easy to implement: we just find the total weight along the path then exponentiate the negative total (because our weights are the negative log of the exchange rates).

def calculate_arb(cycle, g, verbose=True):
    total = 0
    for (p1, p2) in zip(cycle, cycle[1:]):
        total += g[p1][p2]["weight"]
    arb = np.exp(-total) - 1
    if verbose:
        print("Path:", cycle)
        print(f"{arb*100:.2g}%\n")
    return arb


def find_arbitrage(filename="snapshot.csv"):
    df = pd.read_csv(filename, header=0, index_col=0)
    g = nx.DiGraph(-np.log(df).fillna(0).T)

    if nx.negative_edge_cycle(g):
        print("ARBITRAGE FOUND\n" + "=" * 15 + "\n")
        for p in all_negative_cycles(g):
            calculate_arb(p, g)
    else:
        print("No arbitrage opportunities")

Running this function gives the following output:

ARBITRAGE FOUND
===============

Path: ['USDT', 'BAT', 'BTC', 'BNB', 'ZEC', 'USDT']
0.087%

Path: ['BTC', 'XRP', 'USDT', 'BAT', 'BTC']
0.05%

Path: ['BTC', 'BNB', 'ZEC', 'USDT', 'BAT', 'BTC']
0.087%

Path: ['BNB', 'ZEC', 'USDT', 'BAT', 'BTC', 'BNB']
0.087%

Path: ['USDT', 'BAT', 'BTC', 'XRP', 'USDT']
0.05%

0.09% is not exactly a huge amount of money, but it is still risk-free profit, right?

Why wouldn’t this work?

Notice that we haven’t mentioned exchange fees at any point. In fact, Binance charges a standard 0.1% commission on every trade. It is easy to modify our code to incorporate this: we just multiply each exchange rate by 0.999. But we don’t need to compute anything to see that we would certainly be losing much more money than gained from the arbitrage.

Secondly, it is likely that this whole analysis is flawed because of the way the data was collected. The function download_snapshot makes a request for each coin in sequence, taking a few seconds in total. But in these few seconds, prices may move – so really the above “arbitrage” may just be a result of our algorithm selecting some of the price movements. This could be fixed by using timestamps provided by the exchange to ensure that we are looking at the order book for each pair at the exact same moment in time.

Thirdly, we have assumed that you can trade an infinite quantity of the bid and ask. An order consists of a price and a quantity, so we will only be able to fill a limited quantity at the ask price. Thus in practice we would have to look at the top few levels of the order book and consider how much of it we’d eat into.

It is not difficult to extend our methodology to arb between different exchanges. We would just need to aggregate the top of the order book from each exchange, then put the best bid/ask onto the respective edges. Of course, to do run this strategy live would require us to manage our inventory not just on a currency level but per currency per exchange, and factors like the congestion of the bitcoin network would come into play.

Lastly, this analysis has only been for a single snapshot. A proper arbitrage bot would have to constantly look for opportunities simultaneously across multiple order books. I think this could be done by having a websocket stream which keeps the graph updated with the latest quotes, and using a more advanced method for finding negative-weight cycles that does not involve recomputing the shortest paths via Bellman-Ford.

Conclusion

All this begs the question: why is it so hard to find arbitrage? The simple answer is that other people are doing it smarter, better, and (more importantly) faster. With highly optimised algorithms (probably implemented in C++), ‘virtual colocation’ of servers, and proper networking software/hardware, professional market makers are able to exploit these simple arbitrage opportunities extremely rapidly.

In any case, the point of this post was not to develop a functional arbitrage bot but rather to demonstrate the power of graph algorithms in a non-standard use case. Hope you found it as interesting as I did!

02 Mar 2019 · 6 min read   [ ]\

Our goal is to develop a systematic method for detecting arbitrage opportunities by framing the problem in the language of graphs. This is a reasonably large topic, so we’ll split it into two parts. Part 1 (this post) will lay the theoretical groundwork, introducing graph algorithms and giving an overview of their application to currency arbitrage. In we will present an actual implementation of these ideas in python, applied to cryptocurrencies.

Formally, we define a graph as a set of vertices (also called nodes) and edges. These edges can be directed (i.e have little arrows on them) and may have weights. It is completely up to us to decide what the vertices, edges, and weights represent. For example, if we are trying to model a road network we might say that the vertices are cities (or junctions), the edges are the roads themselves, and the weights are the length of the roads. The diagram below shows how a weighted directed graph is typically depicted.

The finds the minimum weight path from a single source vertex to all other vertices on a weighted directed graph. For the purposes of this post, we don’t need to know anything other than the fact that once the algorithm terminates, each vertex is labelled with the total weight of the minimum weight path from the source to that vertex.

Actually, the above statement has an incredibly important caveat. What happens if we try to find the minimum weight path from vertices C to D in the above graph? The obvious guess would be that it is the direct path along the CD edge, which has total weight 2.1. However, notice that we can in fact reduce this further by following the path CDECD, which would have a total weight of -1. But why stop there? If we go around the loop again, we can reduce it stil further. In fact, each time we traverse the loop, our minimum total weight will reduce by 1. Hence this is called a negative-weight cycle, the existence of which means that the shortest path between C and D is not defined.

How can graphs be used to model currency markets? At a very high level, we will assign currencies to different vertices, and let the edge weight represent the exchange rate. A simple example is presented below: in this market, we can convert 1 GBP to 1.27 USD, 1 USD to 1.43 AUD, etc.

We have seen that graphs provide an elegant framework for thinking about currency exchange. Concretely, by representing different currencies as vertices and using the negative log of the exchange rate as the edge weight we can apply shortest-path algorithms to find negative-weight cycles if they exist, which correspond to arbitrage opportunities. In the , we code up the methodology presented in this post and apply it to some real-world data. Should be fun!\

21 Apr 2019 · 17 min read   [ ]\

In the (which should definitely be read first!) we explored how graphs can be used to represent a currency market, and how we might use shortest-path algorithms to discover arbitrage opportunities. Today, we will apply this to real-world data. It should be noted that we are not attempting to build a functional arbitrage bot, but rather to explore how graphs could potentially be used to tackle the problem. Later on we’ll discuss why our methodology is unlikely to result in actionable arbitrage.

The full code for this project can be found in this . If you find this post interesting, don’t forget to leave a star!

For the raw data, I decided to use the which has a load of free data compiled across multiple exchanges. To get started, you’ll need to register to get a free API key.

We will be using the package, an intuitive yet extremely well documented library for dealing with all things graph-related in python.

Firstly, we take negative logs as discussed in the . Secondly, in our dataframe we currently have NaN whenever there is no edge between two vertices. To make a valid nx.DiGraph, we need to set these to zero. Lastly, we transpose the dataframe because NetworkX uses a different row/column convention. We then pass this processed dataframe into the nx.Digraph constructor. Summarised in one line:

quant-finance
Part 2
Bellman-Ford algorithm
next post
quant-finance
previous post
GitHub repo
Star
CryptoCompare API
NetworkX
previous post
LogoGraph algorithms and currency arbitrage, part 1 · Reasonable Deviations
LogoGraph algorithms and currency arbitrage, part 2 · Reasonable Deviations