What is Triangular Arbitrage?
Triangular Arbitrage describes the process of exploiting pricing inefficiencies among three different currency pairs in the foreign exchange market (FOREX). A triangular arbitrage strategy involves three trades, exchanging the initial currency for a second, the second currency for a third, and the third currency for the initial.
In mathematical terms
The same theory applies to crypto-currency markets. E.g.:
Required modules
- import urllib2
- import json
- import time
- import pandas as pd
- from pandas import Series, DataFrame
- import os
- import numpy as np
- from decimal import *
- from urllib2 import URLError
First we connect to the API with urllib2. The data is provided as json format, which I convert to a pdDataFrame and extract the required information: the BID, ASK and the respective volumes.
- def ltcbtc():
- try:
- raw = urllib2.urlopen("http://pubapi.cryptsy.com/api.php?method=singleorderdata&marketid=3").read()
- dictionary = json.loads(raw)
- #BID
- bidraw = dictionary['return']['LTC']['buyorders']
- newformatb = pd.DataFrame(bidraw)
- newnameb = newformatb.rename(columns={'price': 'BID'})
- global finalbid1
- finalbid1 = newnameb[0:1]
- #ASK
- askraw = dictionary['return']['LTC']['sellorders']
- newformata = pd.DataFrame(askraw)
- newnamea = newformata.rename(columns={'price': 'ASK'})
- global finalask1
- finalask1 = newnamea[0:1]
- except URLError, error:
- print error
- def dogeltc():
- try:
- raw = urllib2.urlopen("http://pubapi.cryptsy.com/api.php?method=singleorderdata&marketid=135").read()
- dictionary = json.loads(raw)
- #BID
- bidraw = dictionary['return']['DOGE']['buyorders']
- newformatb = pd.DataFrame(bidraw)
- newnameb = newformatb.rename(columns={'price': 'BID'})
- global finalbid2
- finalbid2 = newnameb[0:1]
- #ASK
- askraw = dictionary['return']['DOGE']['sellorders']
- newformata = pd.DataFrame(askraw)
- newnamea = newformata.rename(columns={'price': 'ASK'})
- global finalask2
- finalask2 = newnamea[0:1]
- except URLError, error:
- print error
- def dogebtc():
- try:
- raw = urllib2.urlopen("http://pubapi.cryptsy.com/api.php?method=singleorderdata&marketid=132").read()
- dictionary = json.loads(raw)
- #BID
- bidraw = dictionary['return']['DOGE']['buyorders']
- newformatb = pd.DataFrame(bidraw)
- newnameb = newformatb.rename(columns={'price': 'BID'})
- global finalbid3
- finalbid3 = newnameb[0:1]
- #ASK
- askraw = dictionary['return']['DOGE']['sellorders']
- newformata = pd.DataFrame(askraw)
- newnamea = newformata.rename(columns={'price': 'ASK'})
- global finalask3
- finalask3 = newnamea[0:1]
- except URLError, error:
- print error
Now that we got all the prices/volumes we will go ''hunting'' for market inefficiencies and initiate trades based on it . The code should be pretty straightforward.
Depending on whether the synthetic pair is over- or undervalued, it will print the maximum volume we can trade and more importantly what PROFIT may be achieved.
The below code assumes that LTC/BTC is undervalued and thus a risk-free profit can be obtained by doing the following:
LTC/BTC undervalued: --> LTC to DOGE --> DOGE to BTC --> BTC to LTC"
- def arbitrage1():
- if True:
- capital = 10
- trade1 = capital / float(finalask2['ASK'])
- currentask1 = float(finalask2['ASK'])
- currentvoluem1 = float(finalask2['total'])
- #DOGE --> BTC i.e. selling DOGE
- trade2 = trade1 * float(finalbid3['BID'])
- currentbid2 = float(finalbid3['BID'])
- currentvoluem2 = float(finalbid3['total'])
- #BTC --> LTC i.e. buying LTC
- trade3 = trade2 / float(finalask1['ASK'])
- currentask3 = float(finalask1['ASK'])
- currentvoluem3 = float(finalask1['total'])
- #PNL
- PNL = trade3 - capital
- pnlp = decimal(PNL*100/capital)
- if PNL > 0:
- print "LTC/BTC undervalued: --> LTC to DOGE --> DOGE to BTC --> BTC to LTC"
- print "DOGE/LTC | ASK:",decimal(currentask1),"|Volume:", decimal(currentvoluem1),"LTC"
- print "DOGE/BTC | BID:",decimal(currentbid2),"| Volume:", decimal(currentvoluem2),"BTC"
- print "LTC/BTC | ASK:",decimal(currentask3),"| Volume:", decimal(currentvoluem3),"BTC"
- print "PNL absolut:", decimal(PNL), "PNL in %", pnlp
- vol1ltc = decimal(currentvoluem1)
- vol2ltc = currentvoluem2 / float(finalbid1['BID'])
- vol3ltc = currentvoluem3 / float(finalbid1['BID'])
- amount = min(vol1ltc, vol2ltc, vol3ltc)
- decamount = decimal(amount)
- print "Sell" ,decamount, "LTC"
- def decimal(x):
- x = Decimal(x).quantize(Decimal('.000000001'),rounding=ROUND_DOWN)
- return x
LTC/BTC overvalued: --> LTC to BTC --> BTC to DOGE --> DOGE to LTC
To complete the program I added the following infinite loop.
- while True:
- print "...Looking for triangular arbitrage opportunity", time.strftime('%I:%M:%S %p %Z')
- ltcbtc()
- dogeltc()
- dogebtc()
- arbitrage1()
- arbitrage2()
- time.sleep(2)
So as you can see at 12:33:10 PM BST we can sell 3.13 LTC for a risk-free profit of 0.62% !
Considering transaction costs of 0.6% (3 * 0.2%) and more importantly low trading volumes, this trading strategy will only be good for tiny profits. Nevertheless it does indeed generate a risk-free profit and you can easily connect a trading bot to the exchange to automate this strategy.
Comments welcome. (Hints to mistakes too!)
EDIT: Another trading opportunity a few hours later. Now with a 16% risk-free profit.