Algorithmic trading with Julia

A detailed version of this article appeared in the Automated Trader magazine.

What makes algorithmic trading particularly challenging is that it
needs to be a polymath to do it well. It requires a unique blend of
mathematics, finance, databases, operating systems, and street
smarts. Julia makes it easier. Prototype in Julia, use the most
sophisticated algorithms to trade once in a while, or the simplest
ones to trade with high frequency, backtest at scale, and deploy in
production extracting the most out of the best hardware. The
JuliaFin suite of
packages makes this entire workflow smooth and
easy. JuliaDB makes it easy to store and query
historical data and write in-database real-time
algorithms. Miletus is a domain
specific language to value complex financial instruments without
programming the mathematics from scratch. While the
Bloomberg API
wrapper package makes it easy to query real-time data from the
Bloomberg terminal, it is equally easy to consume data from other
feeds and sockets in a variety of formats. Finally, with
JuliaRun, all of this
can be put into production and scaled with a single click. This blog
will introduce each of these packages from a trading perspective and
tie it all up with an arbitrage example.

Now, you may say that is all great, but does Julia integrate with
Excel, because otherwise there is no way my users will use it? That’s
why we will start off with a quick overview of
JuliaInXL.

Excel integration – Deployment to front office teams

Regardless of where one sits within the financial services industry,
one product that is ubiquitous is Microsoft® Excel®. Analysts and
traders often want library functionalities to be accessible from
Excel. Julia Computing’s JuliaInXL package provides an extension to
Excel that brings the power of the Julia language and its ecosystem
into the familiar spreadsheet work
environment. JuliaInXL
allows users to launch Julia processes, load modules and execute
functions directly from the Excel UI. In Figure 01 below, we show an
example of how a user can construct new functionality in the IDE,
launch a JuliaInXL server and execute Julia functions against that
JuliaInXL server session.


Figure 01: Julia and Excel working together

Connectors for historical data sources for tick data and aggregated data

To access historical financial data from within Julia there are
options available to either use community-developed packages or
commercially-supported options from Julia Computing. In the set of
community-developed packages, the
Quandl.jl package allows
users to access Quandl’s online financial data feeds using your own
unique authentication token for the Quandl service. Quandl.jl provides
functionality for both searching and retrieving information from
Quandl’s database which covers a broad set of categories for financial
and economic datasets. Similarly, the
YStockData.jl package
allows for retrieving historical securities pricing information from
Yahoo! Finance. (Although the Yahoo! Finance API does not give access
to current data anymore, it is still possible to download historical
data.) As part of the JuliaFin suite of offerings from Julia
Computing, the
Blpapi.jl package
implements a full wrapper interface of Bloomberg Professional’s BLPAPI
for C/C++, as well as high level implementations of the popular bdp
and bdh functions for retrieving historical daily data and intraday
tick and bar functions for retrieving tick level data (Listing 1).

JuliaDB – Store and query historical data. Write in-database real-time algorithms.

Developing an effective trading strategy normally involves testing the
strategy using historical data before going into production using live
data. Building a robust platform for backtesting requires integration
of a number of distinct components for accessing, storing and querying
data libraries for statistical modelling, numerical optimisation and
financial analytics. Additionally, incorporating visualisation
packages can help lead to clear insights. The Julia ecosystem includes
a variety of packages that address each step in developing and
deploying a backtesting workflow.

Efficient warehousing and querying of time series and other structured data

An effective backtesting system not only requires access to historical
financial data, but also needs means of storing, persisting and
querying that data. Financial time series datasets are often highly
structured data, but can also be composed of messy datasets that
include missing values or multiple columns of temporally mismatched
data. Handling financial time series requires data structures that can
be efficiently read, stored, queried and extracted for this type of
naturally ordered data. Algorithms need to be able to handle missing
or non-overlapping data in an effective manner.

In this section, we focus on a package that has recently been
developed and open sourced by Julia Computing, aiming to provide a
user-friendly and high-performing table interface for interacting with
column oriented data
sets. JuliaDB.jl is a
distributed columnar data table implementation that builds on a
framework for parallel, in-memory and out- of-core execution and
provides a column store data table implementation along with fast data
input/output from csv-files.

JuliaDB enables large datasets spread through numerous files across a
cluster of Julia worker processes to be ingested into a single,
distributed table data structure. A JuliaDB table separates columns
into two distinct sets wherein one set forms a sorted index and the
remaining columns are the associated data columns. The table data
structure is equivalent to an N-dimensional sparse array with an
interface that follows Julia’s array API as closely as possible, but
also allows for index values to have non-integer data types. A JuliaDB
table provides a mapping of index tuples to individual elements in one
or more data columns, essentially the individual row elements in the
data columns.

Listing 1

# Create a Bloomberg session over a particular IP
address and port number
IP = “localhost”
Port = 8194
session = createSession(IP, Port)
### Reference data request
# Required parameters of reference data request:
# ticker names
tickers =  [“IBM US Equity”, “AAPL US Equity”]
#  elds requested
 elds = [“PX_Open”, “PX_High”, “PX_Last”]
# Call the bdp function by providing session,
tickers and  elds variables
Response = bdp(session, tickers,  elds)
# The response from bdp function is a Julia type
ReferenceDataResponse object
# Initializing tickers and  elds arrays to be passed
to the bdp function call
tickers = [“IBM US Equity”, “AAPL US Equity”]
 elds = [“PX_Last”, “PX_Open”]
# Getting the response in variable ‘Response’
Response = bdp(Session, tickers,  elds)
# extracting response data by providing ticker and
 eld name
ibmLastPrice = Response[“IBM US Equity”, “PX_Last”]
ibmOpenPrice = Response[“IBM US Equity”, “PX_Open”]
appleLastPrice = Response[“AAPL US Equity”,
                          “PX_Last”]
appleOpenPrice = Response[“AAPL US Equity”,
“PX_Open”]

Listing 2

               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: https://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?help" for help.
  | | | | | | |/ _  |  |
  | | |_| | | | (_| |  |  Version 0.6.0-rc1.0 (2017-05-07 00:00 UTC)
 _/ |\__'_|_|_|\__'_|  |  Official http://julialang.org/ release
|__/                   |  x86_64-pc-linux-gnu

julia> addprocs(20);

julia> @everywhere using JuliaDB
julia> dir = "/home/juser/TrueFX/data";
julia> files = glob("*.csv",dir)
1380-element Array{String,1}:
 "/home/juser/TrueFX/data/AUDJPY-2009-05.csv"
 "/home/juser/TrueFX/data/AUDJPY-2009-06.csv"
 "/home/juser/TrueFX/data/AUDJPY-2009-07.csv"
 "/home/juser/TrueFX/data/AUDJPY-2009-08.csv"
 "/home/juser/TrueFX/data/AUDJPY-2009-09.csv"
 "/home/juser/TrueFX/data/AUDJPY-2009-10.csv"
 "/home/juser/TrueFX/data/AUDJPY-2009-11.csv"
 "/home/juser/TrueFX/data/AUDJPY-2009-12.csv"
 "/home/juser/TrueFX/data/AUDJPY-2010-01.csv"
 
 "/home/juser/TrueFX/data/USDJPY-2016-05.csv"
 "/home/juser/TrueFX/data/USDJPY-2016-06.csv"
 "/home/juser/TrueFX/data/USDJPY-2016-07.csv"
 "/home/juser/TrueFX/data/USDJPY-2016-08.csv"
 "/home/juser/TrueFX/data/USDJPY-2016-09.csv"
 "/home/juser/TrueFX/data/USDJPY-2016-10.csv"
 "/home/juser/TrueFX/data/USDJPY-2016-11.csv"
 "/home/juser/TrueFX/data/USDJPY-2016-12.csv"

julia> a = ingest(files, dir, colnames=["pair","timestamp","bid","ask"], indexcols=[1,2],
                  colparsers=Dict("pair" => TextParse.StrRange, "timestamp" => Dates.DateTime,
                                  "bid" => Float32, "ask" => Float32))

Reading 1380 csv files totalling 237.217 GiB...
DTable with 5539994782 rows in 1380 chunks:

pair       timestamp                bid     ask
───────────────────────────────────┼───────────────
"AUD/JPY"  2009-05-01T00:00:00.31   72.061  72.092
"AUD/JPY"  2009-05-01T00:00:00.318  72.062  72.09
"AUD/JPY"  2009-05-01T00:00:00.361  72.066  72.092
"AUD/JPY"  2009-05-01T00:00:00.528  72.061  72.087
"AUD/JPY"  2009-05-01T00:00:00.547  72.061  72.087
...

julia> a["GBP/USD", DateTime(2016,6,15):Dates.Minute(5):DateTime(2016,7,1)]
DTable with 1 chunks:


pair       timestamp            bid      ask
───────────────────────────────┼─────────────────
"GBP/USD"  2016-06-15T07:40:00  1.41645  1.41657
"GBP/USD"  2016-06-15T10:35:00  1.41995  1.4201
"GBP/USD"  2016-06-17T11:45:00  1.42951  1.42968
"GBP/USD"  2016-06-20T07:40:00  1.45941  1.45954
"GBP/USD"  2016-06-20T18:10:00  1.46716  1.46724

The index columns in JuliaDB tables are automatically sorted
lexicographically from left to right, which allows for extremely fast
data extraction on data sets that have a natural order to their index
values, a common scenario for financial time- series data. JuliaDB
provides a set of functions for efficient selection, aggregation,
permutation and conversion of data along one or more of the index
dimensions of a given table. Whenever possible, operations on the
columns of a JuliaDB table happen in the individual Julia process
where a subset of data is already located. While JuliaDB is fully
capable of automatically resorting data between processes when
necessary, providing the system with advanced knowledge of how on-disk
data sets should be partitioned can improve performance.

Listing 2 is an example of loading JuliaDB across a cluster of 20
Julia worker processes, ingesting more than seven years worth of
foreign exchange rate data that was obtained from
TrueFX. A simple indexing operation is used to
extract a subset of the table.

In an example presented in the final section, we will walk through how
one can go about integrating JuliaDB with other packages from the
Julia ecosystem to build an efficient and high-performing backtesting
system.

Miletus – Model, price and analyze individual as well as baskets of securities

The ability to price and hedge a variety of securities requires access
to an extensive set of financial modelling and simulation tools. These
must reflect the diverse range of possible contract terms that can be
used in defining individual securities and baskets of
securities. Mathematical models need to reflect the conditions under
which the market can operate. When speed of development and execution
are both critical, traders need access to both pre-built libraries of
commonly used contracts and models, as well as functionality for
quickly building models for new contracts under changing market
conditions.

The Julia package ecosystem provides all of the foundational
mathematical and statistical functionality necessary to build
financial models of any desired complexity. In addition it includes a
set of packages specifically focused on the construction, modelling
and time-series analytics of financial securities. The JuliaStats
organisation includes packages covering basic probability and
statistics, model fitting, Monte Carlo analysis and foundational
machine learning tools. With specific regards to time series
analytics, the
MarketTechnicals.jl,
Indicators.jl and
TimeModels.jl packages
implement a variety of algorithms commonly utilised in technical
analysis, including moving averages, momentum and volatility
indicators, rolling statistical calculations and GARCH models.

In the realm of financial contract definition, modelling and pricing,
Julia Computing has developed
Miletus.jl
as part of its
JuliaFin
offering. Miletus is a package that consists of a domain specific
language for financial contract definition inspired by the research
work of Peyton-Jones and Eber. Miletus allows for complex financial
contracts to be constructed from a combination of a few simple
primitive components and operations. When viewed through the lens of
functional programming, this basic set of primitive objects and
operations form a set of ‘combinators’ that can be used to create more
complex financial instruments, including equities, options, currencies
and bonds of various kinds.

In addition, Miletus includes a decoupled set of valuation model
routines that can be applied to combinations of contract
primitives. These combinators and valuation routines are implemented
through the use of Julia’s user-defined types, generic programming and
multiple dispatch capabilities.

Some existing implementations of financial contract modelling
environments (created in languages such as Haskell or OCaml) rely
heavily on pure functional programming for the contract definition
language, but may then switch to a second language (such as C++, Java,
APL) for implementation of valuation processes. Miletus differs in
that it leverages Julia’s strong type system and multiple dispatch
capabilities to both express these contract primitive constructs and
provide for generation of efficient valuation code. As seen elsewhere
in the Julia ecosystem, Miletus solves the two-language problem with
regards to defining and modelling financial contracts.

In Listing 3 we show an example of how to construct and value a basic
European call option in terms of the primitive constructs available in
Miletus, as well as convenient, high- level constructors.

Now we can create a few basic operations using the type constructors
provided by Miletus, shown in Listing 4. These basic primitives can
be combined into higher level operations as displayed in Listing 5.

Listing 3

 # Import the library
julia> using Miletus
       import Miletus: When, Give, Receive, Buy,
                       Both, At, Either, Zero

Listing 4

# Receive an amount of 100 USD
julia> x=Receive(100USD)
Amount
└─100USD
# Pay is the opposite of Receive
julia> x=Pay(100USD)
Give
  └─Amount
    └─100USD
# Models are constructed and valued on a generic
SingleStock type
julia> s=SingleStock()
SingleStock

Listing 5

# Acquisition of a stock by paying 100USD
julia> x=Both(s, Pay(100USD))
Both
  ├─SingleStock
  └─Give
      └─Amount
        └─100USD
# Which is equivalent to buying the stock
julia> x=Buy(s, 100USD)
Both
  ├─SingleStock
  └─Give
    └─Amount
      └─100USD
# The notion of optionality is expressed by a choice
of two outcomes
julia> x=Either(s, Zero())
Either
  ├─SingleStock
  └─Zero

Listing 6

julia> x=When(At(Date("2017-12-25")),
              Receive(100USD))
When
  ├─{==}
   ├─DateObs
   └─2017-12-25
  └─Amount
  └─100USD

Listing 7

julia> x=When(At(Date("2017-12-25")),
              Either(Buy(s, 100USD), Zero()))

When
  ├─{==}
    ├─DateObs
    └─2017-12-25
  └─Either
    ├─Both
      ├─SingleStock
      └─Give
          └─Amount
              └─100USD
    └─Zero



julia> eucall = EuropeanCall(Date("2017-12-25"),
                             SingleStock(), 100USD)
When
  ├─{==}
    ├─DateObs
    └─2017-12-25
  └─Either
    ├─Both
      ├─SingleStock
      └─Give
          └─Amount
              └─100USD
    └─Zero

Another important aspect of any contract is the notion of time. In
Listing 6 we define a temporal condition on which to receive a
payment of 100 USD. Combining that temporal condition with optionality
defines a basic European call option, which is demonstrated in Listing 7.

Listing 8

julia> gbmm = GeomBMModel(today(), 100.0USD, 0.1,
0.05, .15)
Geometric Brownian Motion Model
-------------------------------
S_0 = 100.0USD
T = 2017-03-14
Yield Constant Continuous Curve with r = 0.1,
                                     T = 2017-03-14
Carry Constant Continuous Curve with r = 0.05,
σ = 0.15
julia> value(gbmm, eucall)
7.054679704161716USD

Listing 9

 julia> using Miletus, Gadfly, Colors
        import Miletus: Both, Give, Contract, WhenAt,
                        value

Listing 10

julia> expirydate = Date("2017-12-25")
       startdate  = Date("2017-12-1")
       interestrate = 0.05
       carryrate    = 0.1
       Volatility = 0.15 K1 = 98.0USD
       K2 = 100.0USD
       K3 = 102.0USD
       L = 11 # Layers in the binomial lattice
       price = K1-1USD:0.1USD:K3+1USD

Listing 11

julia> function payoff_curve(c, d::Date, prices)
         payoff  = [value(GeomBMModel(d, x, 0.0, 0.0,
                        0.0), c) for x in prices]
         p = [x.val for x in payoff]
         r = [x.val for x in prices]
         return r, p
       end

To price an option requires a valuation model. In Listing 8 we define a simple Geometric Brownian Motion
model (commonly referred to as the Black-Scholes equation) and value our European call option using that
model.

Having defined a basic call option, more complex payoffs can be
created through combinations of multiple options. Option spreads
consist of various combinations of call and put options having
different strike prices and/or expiry dates. We give examples of a few
different common option spread strategies. First, as shown in Listing 9,
let’s load the packages we will need to construct, visualise and
value our spreads.

Next, let’s define a few parameters that we will use when constructing
the different component options of our spread payoffs and valuation
model, including a range of strike prices we will use to visualise the
payoff curves (Listing 10).Then, in Listing 11, we define a function
that allows for calculating the payoff at expiry.

Listing 12

julia> function butterfly_call(expiry::Date, K1, K2, K3)
         @assert K1 < K2 < K3
         c1 = EuropeanCall(expiry, SingleStock(), K1)
         c2 = EuropeanCall(expiry, SingleStock(), K2)
         c3 = EuropeanCall(expiry, SingleStock(), K3)
         Both(Both(c1,c3), Give(Both(c2,c2)))
       end

julia> bfly1 = butterfly_call(expirydate, K1, K2, K3)

julia> s, p_bfly1 = payoff_curve(bfly1, expirydate, price)
      call₁ = EuropeanCall(expirydate, SingleStock(), K₁)
      call₂ = EuropeanCall(expirydate, SingleStock(), K₂)
      call₃ = EuropeanCall(expirydate, SingleStock(), K₃)
      s₁,cp₁ = payoff_curve(call₁, expirydate, price)
      s₂,cp₂ = payoff_curve(call₂, expirydate, price)
      s₃,cp₃ = payoff_curve(call₃, expirydate, price)
          blk = colorant"black"
          red = colorant"red"
          grn = colorant"green"
          blu = colorant"blue"
          plot(layer(x=s , y=p_bfly1, Geom.line, Theme(default_color=blk, line_width=1.5mm)),
               layer(x=s1,     y=cp₁, Geom.line, Theme(default_color=red, line_width=1.0mm)),
               layer(x=s₃,     y=cp₃, Geom.line, Theme(default_color=grn, line_width=1.0mm)),
               layer(x=s₂,   y=-2cp₂, Geom.line, Theme(default_color=blu, line_width=1.0mm)), Guide.manual_color_key("", ["Butterfly Call", "call1", "call3", "-2call2"],
               ["black", "red", "green", "blue"]),
               Guide.title("Butterfly Call Payoff Curve at Expiry"),
               Guide.xlabel("Stock Price"), Guide.ylabel("Payoff"))

Listing 13

julia> volatility = 0.2
julia> gbmm = GeomBMModel(startdate, K2,
interestrate, carryrate, volatility)
Geometric Brownian Motion Model
-------------------------------
S0 = 100.0USD
T = 2017-12-1
Yield Constant Continuous Curve with r = 0.1,
                                     T = 2017-12-1
Carry Constant Continuous Curve with r = 0.05,
                                     T = 2017-12-1
σ = 0.15

julia> value(gbmm, b y1)
0.40245573232657295USD

Note: Julia allows for using the full unicode character set in its
source code. Special characters can be entered in many ways depending
on the input editor, but the primary method supported on the REPL is
to use TAB-completion with Tex expressions. So, for example, typing
\pi produces π, while typing K\_1 produces K1.

A call butterfly consists of four options. It can be built by buying
two call options at the high and low strike price and selling two call
options at the central strike price.

Figure 02 shows the payoffs for both the call butterfly (in black) and
its constituent call options, produced by the code in Listing 12. The
options can be valued using a variety of models. For example, Listing
13 shows code for valuing the contract using the simple Geometric
Brownian Motion model.

Optimal trading strategies

When developing any trading strategy, optimising with regards to
timing, price, volume, risk and other metrics is key to ensuring
profitable execution. Julia and its package ecosystem have unique
strengths in the area of mathematical optimisation that are not found
in other technical computing language. Optimisation development in
Julia is coordinated through the JuliaOpt,
JuliaNLSolvers and
JuliaDiff communities.

The JuliaOpt and JuliaNLSolvers organisations contain packages, such
as Optim.jl,
LineSearches.jl,
LsqFit.jl and
NLsolve.jl, which are
implemented as liberally-licensed, pure Julia code, as well as
integrations with other best-of- breed commercial and open-source
optimisation libraries implemented in C, C++ and Fortran. The full
list of open- source and commercial optimisation solvers integrated
with Julia can be found on the JuliaOpt home page. Most of these
interface packages have implemented integrations into JuliaOpt’s
MathProgBase.jl
abstraction layer package. This provides users with both high-level,
one-shot functions for linear and mixed-integer programming, as well
as a solver- independent, low-level interface for implementing
advanced techniques requiring the efficient solution of sequential
linear programming problems.


Figure 02: Call butterfly payoff at expiry

MathProgBase provides the solver-independent abstractions needed for
implementation of the JuMP.jl domain specific language for numerical
optimisation. JuMP is an award-
winning, domain-specific language for optimisation in Julia
implemented in the tradition of existing open source and commercial
modelling languages such as AMPL, GAMS, AIMMS, OPL and MPL. Unlike
these existing modelling languages, JuMP’s implementation as a
standard Julia package allows for easy integration of high-level
optimisation modelling within larger analytic workflows. Further down,
we will give an example of a basic FX arbitrage strategy that walks
through the use of JuMP and uses data pulled from JuliaDB.

In addition to traditional linear and nonlinear programming approaches
to mathematical optimisation, the current movement in machine learning
and deep learning is developing along a similar trajectory within
Julia. The JuliaML organisation encompasses low-level interfaces to
existing learning frameworks, such as
MXnet.jl,
TensorFlow.jl and
Knet.jl. Model abstraction
packages allow users to easily switch between frameworks, and high
level modelling tools provide concise representations of machine
learning models.

While early in its development, the
Flux.jl package is a Keras like
package that includes its own DSL for the definition of machine
learning models. It allows for switching between backends like MXNet
or TensorFlow and easily fits into a larger Julia workflow.

Tying it all together

In this section we will show how to tie together a number of distinct
Julia packages into a workflow for determining basic arbitrage
opportunities in the FX markets. As a simple trading strategy for FX
arbitrage, we will be implementing a Julia version of an example from
Cornuejols and Tütüncü,
2006
. In
this strategy, imbalances within exchange rates between multiple
currency pairs can be exploited by setting up a linear programming
optimisation problem defined with JuMP. In this problem structure, for
a given set of exchange rates at a particular point in time, a set of
linear constraint equations is constructed that define all of the
possible relationships for exchanging US dollars, Euros, British
pounds and Japanese yen for a trader starting with one US dollar in
net assets. For this example, additional constraints are added to
limit all currency amounts to be positive and to limit the number of
dollars returned to be 10,000 USD. Our optimisation problem will be
set to maximise the number of dollars returned.

This example builds upon the TrueFX data set that we loaded into a
distributed JuliaDB table previously.To construct our optimisation
problem in Julia, we first need to load a set of packages (Listing
14). In this case we are loading JuMP along with the GLPK.jl and
GLPKMathProgInterface.jl
packages. GLPK.jl is a wrapper
for the open-source Gnu Linear Programming Kit optimisation library
and the
GLPKMathProgInterface.jl
package implements the interface to MathProgBase.jl that enables use
of this solver from JuMP.

Listing 14

julia> using JuMP, GLPK, GLPKMathProgInterface;

Listing 15

julia> function fx_arbitrage(eurusd, eurgbp, eurjpy,
gbpusd, gbpjpy, usdjpy)
         usdeur = 1.0/eurusd
         usdgbp = 1.0/gbpusd
         gbpeur = 1.0/eurgbp
         jpyusd = 1.0/usdjpy
         jpyeur = 1.0/eurjpy
         jpygbp = 1.0/gbpjpy

         m = JuMP.Model(solver = GLPKSolverLP(
                        msg_lev = GLPK.MSG_ERR))

         @variables m begin
           de; dp; dy; ed; ep; ey; pd; pe; py; yd;
           ye; yp; d
         end

         @objective(m, Max, d)

         @constraints(m, begin
           d + de + dp + dy - eurusd*ed - gbpusd*pd
             - jpyusd*yd == 1.0
             ed + ep + ey - usdeur*de - gbpeur*pe
                - jpyeur*ye == 0.0
             pd + pe + py - usdgbp*dp - eurgbp*ep
                - jpygbp*yp == 0.0
             yd + ye + yp - usdjpy*dy - eurjpy*ey
                - gbpjpy*py == 0.0
           d  <= 10000.0
           de >= 0.0
           dp >= 0.0
           dy >= 0.0
           ed >= 0.0
           ep >= 0.0
           ey >= 0.0
           pd >= 0.0
           pe >= 0.0
           py >= 0.0
           yd >= 0.0
           ye >= 0.0
           yp >= 0.0
         end)

         solve(m)

         DE,DP,DY,D = getvalue(de), getvalue(dp),
                      getvalue(dy), getvalue(d)
         ED,EP,EY   = getvalue(ed), getvalue(ep),
                      getvalue(ey)
         PD,PE,PY   = getvalue(pd), getvalue(pe),
                      getvalue(py)
         YD,YE,YP   = getvalue(yd), getvalue(ye),
                      getvalue(yp)

         return D, DE, DP, DY, ED, EP, EY, PD, PE,
                PY, YD, YE, YP
        end
fx_arbitrage (generic function with 1 method)

Next, we will define our optimisation problem within a function using
JuMP. Our function accepts a set of exchange rates as input arguments
and then constructs a JuMP model using GLPK as the linear programming
solver.To fit with the assumptions made in the Cornuejols and
Tütüncü example, we will work with only the bid prices for each
currency pair and also ignore transaction costs.

Listing 16

julia> bids = compute(map(IndexedTables.pick(:bid),
                          a),allowoverlap=false)
DTable with 5539994778 rows in 1380 chunks:

pair       timestamp               
───────────────────────────────────┼───────
"AUD/JPY"  2009-05-01T00:00:00.31   72.061
"AUD/JPY"  2009-05-01T00:00:00.318  72.062
"AUD/JPY"  2009-05-01T00:00:00.361  72.066
"AUD/JPY"  2009-05-01T00:00:00.528  72.061
"AUD/JPY"  2009-05-01T00:00:00.547  72.061
...

julia> @everywhere function  veminutes(t)
         w = trunc(t, Dates.Minute)
         m = Dates.Minute(w).value % 5
         w + Dates.Minute(5-m)
       end

julia> bids_5m = compute(convertdim(bids, 2,
                          veminutes, agg = max))
DTable with 8274322 rows in 1380 chunks:

───────────────────────────────┬───────
 "AUD/JPY" 2009-05-01T00:05:00  72.092
 "AUD/JPY" 2009-05-01T00:10:00  72.26
 "AUD/JPY" 2009-05-01T00:15:00  72.264
 "AUD/JPY" 2009-05-01T00:20:00  72.151
 "AUD/JPY" 2009-05-01T00:25:00  72.21


Figure 03: Currency volumes traded over time

To change our program for use with a different open-source or
commercial optimisation library, one only needs to change the solver
argument used when constructing the JuMP model. Creation of an
optimisation problem with JuMP is as simple as applying a few simple
macros to the model that create new symbolic variables, define an
objective function and apply a set constraints to those symbolic
variables. Once the problem is fully defined, executing the solve
function on the model variable transforms the defined fields of the
model into inputs suitable for use by GLPK. It then executes GLPK’s
linear programming solver and returns an optimal set of currency
values that meet the requirements of our objective function and
constraints. See the JuMP documentation for more information on its
capabilities for the construction and solution of optimisation
problems.

With our optimisation problem defined in Listing 15, we will now
transform and extract a subset of the data on which to execute our
arbitrage algorithm in Listing 16. First, we extract the bid data into
its own table, then we perform a windowing operation over a
five-minute interval for each currency pair to synchronise their
timestamps.

Listing 17

julia> t_5m  = DateTime(2016,6,15):Dates.Minute(5):DateTime(2016,7,1)
2016-06-15T00:00:00:5 minutes:2016-07-01T00:00:00

julia> TS = gather(bids_5m["EUR/USD",t_5m]).index.columns[2];

julia> EURUSD = gather(bids_5m["EUR/USD",t_5m]).data;

julia> EURGBP = gather(bids_5m["EUR/GBP",t_5m]).data;

julia> EURJPY = gather(bids_5m["EUR/JPY",t_5m]).data;

julia> GBPUSD = gather(bids_5m["GBP/USD",t_5m]).data;

julia> GBPJPY = gather(bids_5m["GBP/JPY",t_5m]).data;

julia> USDJPY = gather(bids_5m["USD/JPY",t_5m]).data;

julia> D,DE,DP,DY,ED,EP,EY,PD,PE,PY,YD,YE,YP = fx_arbitrage(EURUSD[1], EURGBP[1], EURJPY[1], GBPUSD[1],
GBPJPY[1], USDJPY[1])
(10000.0, 1.5005627439278532e7, 0.0, 0.0, 0.0, 1.3384616979325693e7, 0.0, 1.063233813089466e7, 0.0, 0.0, 0.0,
0.0, 0.0)

julia> FM  = map(fx_arbitrage, EURUSD, EURGBP, EURJPY, GBPUSD, GBPJPY, USDJPY);

julia> function fx_arbitrage_arrays(A,TS)

         M = length(A)
         D  = Array{Float64}(M)
         DE = Array{Float64}(M)
         DP = Array{Float64}(M)
         DY = Array{Float64}(M)
         ED = Array{Float64}(M)
         EP = Array{Float64}(M)
         EY = Array{Float64}(M)
         PD = Array{Float64}(M)
         PE = Array{Float64}(M)
         PY = Array{Float64}(M)
         YD = Array{Float64}(M)
         YE = Array{Float64}(M)
         YP = Array{Float64}(M)
         for m = 1:M
           D[m],DE[m],DP[m],DY[m],ED[m],EP[m],EY[m],PD[m],PE[m],PY[m],YD[m],YE[m], YP[m]  = (A[m]...)
         end
         IndexedTable(Columns(timestamp = TS),
end
Columns(DE=DE,DP=DP,DY=DY,ED=ED,EP=EP,PD=PD,PE=PE,PY=PY,YD=YD,YE=YE,YP=YP))
fx_arbitrage_arrays (generic function with 1 method)

julia> FM_arbitrage  = fx_arbitrage_arrays(FM, TS)

Next, we index into the table for each currency pair over a two-week
subset of five-minute intervals and extract the resulting data columns
into their own arrays.The rate values in each of these arrays can be
passed individually into a single execution of our arbitrage
optimisation routine, or the routine can be mapped over every element
of these arrays. Finally, as shown in Listing 17, we execute a
separate routine that constructs a new IndexedTable for display of the
currencies exchanged as part of each optimisation at every timestamp
and plot the currency volumes traded over time for each trade in
Figure 03.

Summing up – Why Julia for algorithmic trading

  • Julia provides a high productivity and high performance language for both financial analytics and infrastructure.
  • Julia solves the two-language problem for both development and deployment of automated trading systems.
  • Julia allows easy development of libraries both within the Julia package ecosystem and by using other language functionality from the outside.
  • Julia’s package ecosystem covers the entire analytics and backtesting workflow.

We have shown how to create financial models in Julia and the
performance of their implementation. Julia’s approach to technical
computing has excited hundreds of thousands of quantitative
programmers worldwide and we hope you will join their ranks.