Note: You won't make a killing with this method, otherwise I would not be writing this post.
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
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
Assuming an efficient market, the "synthetic" EURUSD (i.e. EURGBP * GBPUSD) should be the same as EURUSD. Should because a triangular arbitrage opportunity arises when this is NOT the case.
The same theory applies to crypto-currency markets. E.g.:
The same theory applies to crypto-currency markets. E.g.:
Connecting to the exchange's API
Required modules
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
Getting Bid/Ask and Trading Volumes for LTC/BTC
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.
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
The same applies for DOGE/LTC
- 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
And DOGE/BTC
- 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
Let the Arbitrage begin...
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"
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"
For better readability, I am converting the data to decimals. That is the "decimal" you can read in the code above.
- def decimal(x):
- x = Decimal(x).quantize(Decimal('.000000001'),rounding=ROUND_DOWN)
- return x
The same logic applies for a situation where LTC/BTC is overvalued. We only need to take a different "route". I.e.:
LTC/BTC overvalued: --> LTC to BTC --> BTC to DOGE --> DOGE to LTC
To complete the program I added the following infinite loop.
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)
When we now run the program, it will notify us as soon as an arbitrage opportunity arises (PNL > 0 BEFORE transaction costs). Sample Output:
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% !
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% !
Conclusion
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.
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.