The Gateway to Algorithmic and Automated Trading

Julia - A new language for technical computing

Published in Automated Trader Magazine Issue 43 Q2 2017

Historically, developers have faced a trade-off between the ease of use of scripting languages and the power and flexibility of lower-level languages. Julia offers the best of both worlds - and allows easy interoperability with existing languages.


Avik Sengupta

Avik Sengupta has worked on risk and trading systems in investment banking for many years, mostly using Java interspersed with snippets of the exotic R and K languages. This experience left him wondering whether there were better things out there. Avik's quest concluded with the appearance of Julia in 2012. He has been happily coding in Julia and contributing to it ever since.


Simon Byrne

Dr Simon Byrne is a quantitative software developer at Julia Computing, where he implements numerical routines for financial and statistical models. Simon has a PhD in Mathematics from the University of Cambridge and has extensive experience in computational statistics and machine learning in both academia and industry. He has been contributing to the Julia project since 2012.

The programming language Julia started from a simple observation. Traditionally, programming languages that focused on numerical computing could be split into two distinct groups: a set of static languages (for example, C, C++, Fortran) that are fast for execution, but slow for development, and another set of dynamic languages (for example, Python, R, Matlab) that are often slow in their execution, but enable rapid development. The creators of Julia asked themselves if such a dichotomy was really necessary. Was there some sort of natural law preventing a numerical programming language from being both high-performing and productive? Overcoming the performance deficiencies of existing high-level scripting languages used in technical computing often requires programmers to choose between two options: either to re-implement portions of their code in low-level systems languages using interfaces provided by the high-level language, or to rewrite completely their high-level language application within a low-level language. To solve this 'two-language problem' a single language is required that addresses both the needs of the computer when constructing code that can be executed extremely fast, as well as the human desire for writing high-level expressive code that captures mathematical ideas in a concise and generic manner.

Julia provides a solution to this two-language problem. It enables developers and end-users from numerous fields to harness this combination of high productivity and high performance to help move ideas from conception to production at speeds that were not previously possible.

Julia has been used in many sectors and industries, but financial services is one of its most natural habitats. It has been used by organisations large and small to calculate regulatory capital, value portfolios and design trading strategies. In this article, we will demonstrate one such example use case, calculating the arbitrage opportunities in FX cross rates. We will show how a complex optimisation problem can be implemented in a few lines of Julia code.

To start with, we will provide a brief introduction to Julia and its capabilities, as many readers might not yet be familiar with the language.

Julia's history

Julia began its life in 2009 as an academic collaboration among the project's creators Jeff Bezanson, Alan Edelman, Stefan Karpinski and Viral Shah to explore a solution to the two-language problem in technical computing. In February 2012, the project was announced publicly as an open-source project, freely downloadable and usable under an MIT license. Since that first public announcement, the Julia language community itself has grown to include over 500 contributors to the base language. It has spawned an ecosystem of over 1,300 packages in diverse topics including foundational mathematics (statistics, optimisation, differential equations), financial analytics, machine learning, bioinformatics, astrophysics, parallel and distributed computing, systems infrastructure (cloud management, identity management) and many other areas. The Julia user community currently numbers in the hundreds of thousands and is growing rapidly.

With the growing ecosystem and community, interest in the language has expanded beyond pure academic research. In 2015, Julia Computing, Inc. was founded by the creators based on industry requests for products, support and professional services centred on the Julia language ecosystem.

Julia's capabilities

Addressing the two-language problem necessitates careful design choices that balance the needs of the human and the computer. Julia's ability to achieve both high performance and high productivity involve the careful combination of the following features:

  1. An expressive type system, allowing optional type annotations
  2. Multiple dispatch using these types to select implementations
  3. Metaprogramming for code generation
  4. A dataflow type inference algorithm allowing types of most expressions to be inferred
  5. Aggressive code specialisation against run-time types
  6. Just-in-time (JIT) compilation using the low-level virtual machine (LLVM) compiler framework
  7. Carefully written libraries that leverage the language design (points 1-6 above)

Points 1, 2 and 3 above are language features that are primarily concerned with how the human expresses their intentions in code, while points 4, 5 and 6 are mostly about language implementation and compiler internals addressing the needs of the computer. Point 7 involves bringing everything together to enable the development of high performance libraries in Julia. An extensive discussion of these points can be found in Bezanson et al. (2015) as well as references cited therein.


To demonstrate the type of performance that can be achieved with pure Julia code, we will perform a simple benchmark of the sum function, comparing Julia's performance to implementations in both C and Python. This particular example is derived from a lecture given by Prof Steven Johnson and refined by Prof David P. Sanders and Prof Alan Edelman. In this example, we show a simple computation that sums one million numbers. While we use and implement a function that is available in all standard libraries, this code is instructive in demonstrating the power of Julia's language design when using modern hardware.

To begin, let's create a sample vector of one million uniformly distributed random numbers on the interval from 0 to 1, and execute the version of the sum function included as part of a standard distribution of Julia (see Listing 01).

julia> a = rand(10^7) # 1D vector of random numbers,
                        uniform on [0,1)
10000000-element Array{Float64,1}:
julia> sum(a)

Listing 01

To build up a comparison of distinct language implementations, we will utilise Julia's capabilities for integrating with a number of distinct programming languages, as will be discussed in more detail later on. We will perform timing measurements of our execution for each language implementation using the BenchmarkTools.jl package, a framework for performing benchmarking that takes into account sampling noise over multiple executions of a given function.

Details of this implementation are given in the Appendix. For the moment, let us look at the results. We implemented six different approaches: 1) a handwritten custom implementation in C, 2) using the built-in function in NumPy, 3) a custom implementation in Julia, 4) a hand coded custom implementation in Python, 5) the built-in function in Python, and 6) the built-in function in Julia. Listing 02 shows the time, in seconds, for summing the million number array.

"C"                   => 8.14397
"Python numpy"        => 3.91977
"Julia hand-written"  => 8.14976
"Python hand-written" => 1285.9
"Python built-in"     => 73.1252
"Julia built-in"      => 3.77031

Listing 02

There are a few points to consider when analysing these numbers. One is that a handwritten Julia implementation is as fast as a handwritten C implementation. A Python program implementing the same computation is many orders of magnitude slower.

One might well ask why anyone would implement the sum function, especially when the built-in version in NumPy is fast. One response, of course, is that this is a demonstration and that not every function is built in. When you have to write your own implementation, Julia can be orders of magnitude faster. Notice also that the built-in Python implementation is much slower. The NumPy implementation is as fast, but it is actually written in C, while the Julia built-in function is written in pure Julia. This shows how easily one can achieve exceptional performance in Julia without having to fall back to low-level languages for performance critical code.

Composable package ecosystem

As mentioned above, the Julia package ecosystem currently consists of over 1,300 packages that have been publicly registered with Julia's main package repository. These packages contain a wealth of functionality for everything from interfaces to existing financial data feeds and databases, pure Julia data table and database implementations, file parsers and writers for varying file formats, cloud system management utilities, advanced statistical analysis and numerical optimisation libraries, machine learning frameworks, signal processing toolkits, differential equation solvers, visualisation packages, desktop windowing systems, web user interface development tools, bioinformatics packages, parallel and distributed computing functionality, software testing frameworks and many other application areas.

Julia also includes a built-in package manager, Pkg, that allows for easy download, installation and dependency version resolution of all registered Julia packages. The combination of Julia's ease of package management and highly expressive language design allows for simple composability of applications from numerous pre-existing libraries. The full list of registered Julia packages is available at

Language interoperability for polyglot programming projects

While the Julia ecosystem is growing rapidly, existing programming languages have decades of development history and extensive pre-existing libraries that have been written, debugged and optimised in their own environments. Julia and its package ecosystem make it easy to interoperate with a number of programming languages, including C, C++, Fortran, Python, Java and R, which allows for simple reuse of this functionality from within Julia. Below we will walk through a sampling of these external language interfaces.

C wrapper: ccall

When calling into C or Fortran libraries from Julia, there is no need to write custom code within those languages written to a specific Julia API. The ccall function allows for users to call directly into functions that have been exported from shared libraries by providing only the input and output variable arguments and their associated type signatures. Below, in Listing 03, we show an example of calling the getenv function from libc on a Unix system to obtain a pointer to the current environment variable value for "SHELL".

julia> path = ccall((:getenv, "libc"), Cstring,
                    (Cstring,), "SHELL")

julia> unsafe_string(path)

Listing 03

C++ wrappers: Cxx.jl and CxxWrap.jl

To interact with code written in C++, there are two packages that approach the interface in slightly different manners. The Cxx.jl package leverages Julia's use of LLVM to interact with Clang to allow for both compiling new C++ code directly from within a Julia session as well as to load existing shared libraries and header files implemented in C++. Cxx.jl uniquely provides compilation and execution of new and existing C++ code from Julia, but also functionality that transforms the Julia Read-Eval-Print Loop (REPL) into a C++ REPL. A user can dynamically change REPL modes between Julia and C++, while interactively developing functionality that interoperates between languages, an example of which is shown in Listing 04.

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

julia> using Cxx

C++ > // Press ' #include 

C++ > std::cout << "Welcome to Cxx.jl" << std::endl;
Welcome to Cxx.jl

C++ > std::string cxx = "C++";

julia> println("Combine Julia and ", bytestring(icxx"cxx;"))
Combine Julia and C++

Listing 04

The CxxWrap.jl package takes a more traditional approach to the calling of C++ from a dynamic language by enabling the development of interface code completely within C++, coupled with loading of the compiled shared library in one line of Julia code. Loading a compiled, shared library makes use of ccall to load Julia interface functions written in C++. This approach is similar to how the Boost.Python project makes C++ code accessible to Python.

Python wrapper: PyCall.jl

As shown in the benchmarking example above, the PyCall.jl package provides full interoperability between Julia and Python, allowing users to load any Python package within Julia. By fully utilising the Python and NumPy object models, any valid Python object can be constructed within Julia, and all NumPy and Julia arrays can share their underlying data without needing to make copies of their data between language runtime environments. A notable package leveraging PyCall.jl to bring functionality into the Julia ecosystem is the PyPlot.jl package that wraps the popular matplotlib visualisation library. Other popular Julia packages wrapping various Python libraries include SymPy.jl, Pandas.jl, Conda.jl and Seaborn.jl, which can all be found within the JuliaPy organisation on GitHub.

Java wrapper: JavaCall.jl

The JavaCall.jl package provides functionality for launching an installed JVM, importing Java packages into that JVM instance and calling functions within those Java libraries. The interface provided for calling Java functions, jcall, is modelled on the ccall interface for calling C and Fortran functions directly from Julia. JavaCall.jl acts as the basis for packages such as: Spark.jl for using Julia functionality on Spark clusters; JDBC.jl for accessing databases via their JDBC drivers; and Taro.jl for reading and writing Excel files via Apache POI.

R wrapper: RCall.jl

The RCall.jl package handles interoperation between Julia and R environments, allowing R objects to be constructed from Julia, and R functions to be called from Julia. Like the Cxx package, RCall introduces functionality for an R REPL mode, such that the interactive prompt in a terminal session can be easily changed from a Julia mode to an R mode (and back) via a single keystroke. Functionality is provided for transferring data between the R and Julia environments and for converting between their respective object models.

Embedding Julia in other languages

In addition, Julia provides an API for C that allows for the embedding of Julia code from any language that can call C. Using this functionality, a few convenience packages have been developed for both Python (pyjulia) and Matlab (mexjulia) that ease the development of calling Julia from these environments.

Hardware and cloud diversity

Julia has been ported to run on a variety of computing architectures encompassing CPUs, GPUs, Intel Xeon Phi, IBM Power and ARM processors. It has been executed successfully on both small, low-powered computing systems such as RasberryPi, and large, distributed HPC systems such as the Cray XC40 (NERSC's Cori supercomputer) and IBM and Nvidia's Minsky platform. JuliaBox and JuliaRun have also been successfully deployed on Amazon Web Services, Google Compute Engine and Microsoft Azure.

Packages for Effective and Efficient BackTesting with Julia

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.

Connection to 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 05).

# 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"]

# fields requested
fields = ["PX_Open", "PX_High", "PX_Last"]

# Call the bdp function by providing session, tickers and fields variables
Response = bdp(session, tickers, fields)

# The response from bdp function is a Julia type ReferenceDataResponse object

# Initialising tickers and fields arrays to be passed to the bdp function call
tickers = ["IBM US Equity", "AAPL US Equity"]
fields = ["PX_Last", "PX_Open"]

# Getting the response in variable 'Response'
Response = bdp(Session, tickers, fields)

# extracting response data by providing ticker and field name
ibmLastPrice = Response["IBM US Equity", "PX_Last"]
ibmOpenPrice = Response["IBM US Equity", "PX_Open"]
appleLastPrice = Response["AAPL US Equity",
appleOpenPrice = Response["AAPL US Equity",

Listing 05

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.

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 06 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.

julia> addprocs(20);

julia> using JuliaDB

julia> dir = "/home/juser/TrueFX/data";

julia> files = glob("*.csv",dir)
1380-element Array{String,1}:

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

Listing 06

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.

Modelling, pricing and analysis of individual securities and 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 07 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.

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

Listing 07

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

# Receive an amount of 100 USD
julia> x=Receive(100USD)
# Pay is the opposite of Receive
julia> x=Pay(100USD)

# Models are constructed and valued on a generic SingleStock type
julia> s=SingleStock()

Listing 08

# Acquisition of a stock by paying 100USD
julia> x=Both(s, Pay(100USD))

# Which is equivalent to buying the stock
julia> x=Buy(s, 100USD)

# The notion of optionality is expressed by a choice of two outcomes
julia> x=Either(s, Zero())

Listing 09

Another important aspect of any contract is the notion of time. In Listing 10 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 11.

julia> x=When(At(Date("2017-12-25")),
  ? ??DateObs
  ? ??2017-12-25

Listing 10

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

julia> eucall = EuropeanCall(Date("2017-12-25"),
                             SingleStock(), 100USD)
  ?  ??DateObs
  ?  ??2017-12-25
    ?  ??SingleStock
    ?  ??Give
    ?     ??Amount
    ?       ??100USD

Listing 11

To price an option requires a valuation model. In Listing 12 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.

julia> gbmm = GeomBMModel(today(), 100.0USD, 0.1, 0.05, .15)
Geometric Brownian Motion Model
S? = 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,
                                     T = 2017-03-14
? = 0.15

julia> value(gbmm, eucall)

Listing 12

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 13, let's load the packages we will need to construct, visualise and value our spreads.

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

Listing 13

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 14). Then, in Listing 15, we define a function that allows for calculating the payoff at expiry.

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 K?.

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

Listing 14

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

Listing 15

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 01 shows the payoffs for both the call butterfly (in black) and its constituent call options, produced by the code in Listing 16. The options can be valued using a variety of models. For example, Listing 17 shows code for valuing the contract using the simple Geometric Brownian Motion model.

Figure 01

Figure 01: Call butterfly payoff at expiry

julia> function butterfly_call(expiry::Date, K?, K?, K?)
         @assert K? < K? < K?
         c? = EuropeanCall(expiry, SingleStock(), K?)
         c? = EuropeanCall(expiry, SingleStock(), K?)
         c? = EuropeanCall(expiry, SingleStock(), K?)
         Both(Both(c?,c?), Give(Both(c?,c?)))

julia> bfly? = butterfly_call(expirydate, K?, K?, K?)

julia> s, p_bfly? = payoff_curve(bfly?, expirydate, price)
         blk = colorant"black"
         red = colorant"red"
         grn = colorant"green"
         blu = colorant"blue"
         plot(layer(x=s , y=p_bfly?, Geom.line, Theme(default_color=blk, line_width=1.5mm)),
              layer(x=s?,    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", "call?", "call?", "-2call?"],
              ["black", "red", "green", "blue"]),
              Guide.title("Butterfly Call Payoff Curve at Expiry"),
              Guide.xlabel("Stock Price"), Guide.ylabel("Payoff"))

Listing 16

julia> gbmm = GeomBMModel(startdate, K?, interestrate, carryrate, volatility)
Geometric Brownian Motion Model
S? = 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, bfly?)

Listing 17

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.

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.

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 02, we show an example of how a user can construct new functionality within the Juno IDE, launch a JuliaInXL server and execute Julia functions against that JuliaInXL server session.

Figure 02

Figure 02: Workstation running JuliaInXL

Example implementation of a trading strategy and backtesting system

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 18). 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.

julia> using JuMP, GLPK, GLPKMathProgInterface;

Listing 18

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.

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 19, we will now transform and extract a subset of the data on which to execute our arbitrage algorithm in Listing 20. 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.

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

         @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  = 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


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

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

Listing 19

julia> bids = compute(map(IndexedTables.pick(:bid),
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 fiveminutes(t)
         w = trunc(t, Dates.Minute)
         m = Dates.Minute(w).value % 5
         w + Dates.Minute(5-m)

julia> bids_5m = compute(convertdim(bids, 2,
                         fiveminutes, 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

Listing 20

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 21, 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.

Figure 03

Figure 03: Currency volumes traded over time

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]...)

         IndexedTable(Columns(timestamp = TS), 
fx_arbitrage_arrays (generic function with 1 method)

julia> FM_arbitrage  = fx_arbitrage_arrays(FM, TS)
timestamp           ? DE          DP         DY         ED            EP         PD         PE            PY   YD   YE         YP
2016-06-15T00:00:00 ? 1.50056e7   0.0        0.0        0.0           1.33846e7  1.06323e7  0.0           0.0  0.0  0.0        0.0
2016-06-15T00:05:00 ? 2.91127e7   0.0        0.0        0.0           2.5967e7   2.06202e7  0.0           0.0  0.0  0.0        0.0
2016-06-15T00:10:00 ? 0.0         0.0        6.78883e7  0.0           6.05571e7  4.80666e7  0.0           0.0  0.0  7.20974e9  0.0
2016-06-15T00:15:00 ? 5.60457e7   0.0        0.0        0.0           4.9985e7   3.96936e7  0.0           0.0  0.0  0.0        0.0
2016-06-15T00:20:00 ? 2.10401e-8  0.0        1.21634e8  0.0           1.085e8    8.61455e7  -3.07009e-9   0.0  0.0  1.2907e10  0.0
2016-06-15T00:25:00 ? 2.26519e7   0.0        0.0        0.0           2.02061e7  1.60471e7  0.0           0.0  0.0  0.0        0.0
2016-06-15T00:30:00 ? 5.39559e7   0.0        0.0        0.0           4.81311e7  3.82214e7  0.0           0.0  0.0  0.0        0.0
2016-06-15T00:35:00 ? 0.0         0.0        3.75528e7  0.0           3.35051e7  2.66071e7  0.0           0.0  0.0  3.98349e9  0.0
2016-06-15T00:40:00 ? 0.0         8.76471e7  0.0        7.8185e7      0.0        0.0        6.20906e7     0.0  0.0  0.0        0.0
2016-06-15T00:45:00 ? 0.0         0.0        4.24261e7  0.0           3.78587e7  3.00658e7  0.0           0.0  0.0  4.49844e9  0.0
2016-06-30T23:15:00 ? 7.22491e7   0.0        0.0        -8.27181e-25  6.50506e7  5.41774e7  6.20522e-9    0.0  0.0  0.0        0.0
2016-06-30T23:20:00 ? 1.92581e7   0.0        0.0        0.0           1.73411e7  1.44409e7  0.0           0.0  0.0  0.0        0.0
2016-06-30T23:25:00 ? 5.96171e7   0.0        0.0        0.0           5.36767e7  4.46842e7  0.0           0.0  0.0  0.0        0.0
2016-06-30T23:30:00 ? 0.0         0.0        6.63635e7  0.0           5.97565e7  4.97527e7  -6.20328e-9   0.0  0.0  6.84427e9  0.0
2016-06-30T23:35:00 ? 2.91322e7   0.0        0.0        0.0           2.62297e7  2.18359e7  0.0           0.0  0.0  0.0        0.0
2016-06-30T23:40:00 ? 9.99015e7   0.0        0.0        0.0           8.99455e7  7.48715e7  0.0           0.0  0.0  0.0        0.0
2016-06-30T23:45:00 ? 4.59924e7   0.0        0.0        0.0           4.141e7    3.44763e7  0.0           0.0  0.0  0.0        0.0
2016-06-30T23:50:00 ? 0.0         0.0        4.40287e7  0.0           3.96444e7  3.29977e7  0.0           0.0  0.0  4.54226e9  0.0
2016-06-30T23:55:00 ? 9.12517e6   0.0        0.0        0.0           8.21621e6  6.84534e6  -7.75931e-10  0.0  0.0  0.0        0.0
2016-07-01T00:00:00 ? 4.2625e7    0.0        0.0        0.0           3.83805e7  3.1984e7   0.0           0.0  0.0  0.0        0.0

Listing 21

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.

Summary of Julia's benefits

  • 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.

Appendix: Performance comparison of Julia programs with C and Python

In this appendix, we describe a few detailed benchmarks which validate and explain some of the performance characteristics of Julia code.

First, let's construct an implementation of sum in C that operates on floating point double values. In the code segment in Listing A-01, we construct our C implementation within a string, call out to the operating system to compile that C code into a library using gcc, and then define a function that calls into that C library from Julia using the ccall interface.

julia> using BenchmarkTools

julia> C_code = """
double c_sum(size_t n, double *X) {
    double s = 0.0;
    for (size_t i = 0; i < n; ++i) {
        s += X[i];
    return s;

julia> const Clib = tempname()   # make a temporary file

# compile to a shared library by piping C_code to gcc
# (works only if you have gcc installed):

julia> open('gcc -std=c99 -fPIC -O3 -msse3 -xc -shared 
             -o $(Clib * "." * Libdl.dlext) -', "w") do f
         print(f, C_code) 

# define a Julia function that calls the C function:
julia> c_sum(X::Array{Float64}) = ccall(("c_sum", Clib), Float64,
                                 (Csize_t, Ptr{Float64}), length(X), X)

Listing A-01

For each implementation of a sum function, we check that the summation produced is approximately equal to the value calculated by Julia's built-in sum function, and then perform a benchmarking operation with the @benchmark macro. The example in Listing A-02 provides our baseline numbers for our hand implemented summation in C.

julia> c_sum(a) ? sum(a) # type approx and then  to get the ? symbolb

julia> c_bench = @benchmark c_sum($a);

julia> println("C: Fastest time was $(minimum(c_bench.times) / 1e6) msec")
C: Fastest time was 8.143967 msec

julia> d = Dict();  # a "dictionary", i.e. an associative array

julia> d["C"] = minimum(c_bench.times) / 1e6  # in milliseconds
Dict{Any,Any} with 1 entry:
  "C" => 8.14397

Listing A-02

In the next few examples, we make use of Julia's PyCall.jl package. PyCall allows for loading any Python package directly within Julia. We will compare three distinct implementations of sum in Python, the first based on summing numbers in a Python list, a second version that operates on NumPy arrays, and a third version that implements sum by hand in Python. The timing differences will show how accessing predefined functions on data structures that have been optimised (in C) outperform code implemented directly in Python.

In the first example in Listing A-03, we use a low-level PyCall function to get direct access to a Python list, since PyCall automatically converts Julia arrays to NumPy arrays.

julia> using PyCall

julia> apy_list = PyCall.array2py(a, 1, 1);

# get the Python built-in "sum" function:
julia> pysum = pybuiltin("sum")

julia> pysum(a)

julia> pysum(a) ? sum(a)

julia> py_list_bench = @benchmark $pysum($apy_list);

julia> d["Python built-in"] = minimum(
                          py_list_bench.times) / 1e6
Dict{Any,Any} with 2 entries:
  "C"               => 8.14397
  "Python built-in" => 73.1252

Listing A-03

Next, we make use of the Conda.jl package to load NumPy into our environment, and execute NumPy's sum function directly on our Julia vector, which PyCall automatically converts into a NumPy array pointing at the same location in memory (Listing A-04).

julia> using Conda

julia> Conda.add("numpy")
Fetching package metadata ...........
Solving package specifications: .

# All requested packages already installed.
# packages in environment at /home/juser/.julia/v0.5/Conda/deps/usr:
numpy                     1.12.1                   py27_0

julia> numpy_sum = pyimport("numpy")["sum"];

julia> apy_numpy = PyObject(a); # converts to a
                                  numpy array by
julia> py_numpy_bench = @benchmark $numpy_sum(

julia> numpy_sum(apy_list)

julia> numpy_sum(apy_list) ? sum(a)

julia> d["Python numpy"] = minimum(
                         py_numpy_bench.times) / 1e6
Dict{Any,Any} with 3 entries:
  "C"               => 8.14397
  "Python numpy"    => 3.91977
  "Python built-in" => 73.1252

Listing A-04

A hand-written implementation of a Python sum is defined within the first string macro in Listing A-05, and access to the constructed Python object for calling that function from Julia is in the second string macro.

julia> py"""
def py_sum(a):
    s = 0.0
    for x in a:
        s = s + x
    return s

julia> sum_py = py"py_sum"

julia> py_hand = @benchmark $sum_py($apy_list);

julia> sum_py(apy_list)

julia> sum_py(apy_list) ? sum(a)

julia> d["Python hand-written"] = minimum(
                                py_hand.times) / 1e6
Dict{Any,Any} with 4 entries:
  "C"                   => 8.14397
  "Python numpy"        => 3.91977
  "Python hand-written" => 1285.9
  "Python built-in"     => 73.1252

Listing A-05

In the final section we benchmark both the sum function that is distributed as part of the Julia base library, as well as a hand implemented sum function.

As can be seen in the resulting timing comparisons in Listing A-06, the performance of generically implemented Julia versions compare favourably with both our hand-written, statically compiled version implemented in C, as well as the optimised NumPy version, which is also statically compiled and implemented in C for use with only specific data types.

julia> j_bench = @benchmark sum($a);

julia> d["Julia built-in"] = minimum(
                           j_bench.times) / 1e6;
Dict{Any,Any} with 5 entries:
  "C"                   => 8.14397
  "Python numpy"        => 3.91977
  "Python hand-written" => 1285.9
  "Python built-in"     => 73.1252
  "Julia built-in"      => 3.77031

julia> function mysum(A)   
         s = zero(eltype(A))
         for a in A
           s += a
mysum (generic function with 1 method)

julia> j_bench_hand = @benchmark mysum($a);

julia> d["Julia hand-written"] = minimum(j_bench_hand.times) / 1e6
Dict{Any,Any} with 6 entries:
  "C"                   => 8.14397
  "Python numpy"        => 3.91977
  "Julia hand-written"  => 8.14976
  "Python hand-written" => 1285.9
  "Python built-in"     => 73.1252
  "Julia built-in"      => 3.77031

Listing A-06





Julia: A fresh approach to numerical computing

Bezanson, J., Edelman, A., Karpinski, S. & Shah, V.


Optimization methods in finance

Cornuejols, G. & Tütüncü, R.


Cambridge University Press

How to write a financial contract

Peyton Jones, S. L. & Eber, J.-M.