Here is the latest from Julia Computing
BL
G

Algorithmic Trading with Julia

22 August 2017 | Avik Sengupta and Simon Byrne

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<TAB> produces π, while typing K_1<TAB> 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.

Recent posts

Eindhoven Julia Meetup
06 March 2023 | JuliaHub
Newsletter February 2023 - JuliaCon Tickets Available!
21 February 2023 | JuliaHub
11 Years of Julia - Happy Valentine's Day!
14 February 2023 | JuliaHub