Trading in the Hivemind El Niño Prediction Market II: the API

Trading in the Hivemind El Niño Prediction Market II: the APIThe Agora API allows trading to be automated and makes it easier to integrate the output of predictive models into trading decisions.

Mark RoulstonBlockedUnblockFollowFollowingMar 22IntroductionIn a previous post we looked at how the Agora user interface can be used to create contracts and trade them in the Hivemind El Niño-Southern Oscillation (ENSO) Prediction Market.

In this post we will introduce the Agora API which allows trading to be done programmatically.

This allows us to automate trading and also makes it easier to integrate the output of predictive models for ENSO directly into our trading decisions.

This introduction will use R but the API can also be used via other languages, such as Python.

Required packagesInterfacing with the Agora API via R requires the httr and jsonlite packages.

We will also use some functions from zoo.

require(httr)require(jsonlite)require(zoo)Some wrapper functionsFor convenience the following functions wrap calls to the Agora API in R functions.

get.

authentication.

token <- function( auth.

api, email=NULL, password=NULL ) { auth.

request <- list(email=email,password=password) authentication <- fromJSON(content(POST(url=sprintf("%s/api/login",auth.

api),body=auth.

request,accept_json()),as="text")) auth.

token <- paste("Bearer",authentication$token) return(auth.

token)}get.

market <- function( agora.

api, auth.

token, partition.

id, market.

id ) { market <- fromJSON(content(GET(url=sprintf("%s/%s/markets/%s",agora.

api,partition.

id,market.

id),accept_json(),add_headers(Authorization=auth.

token)),as="text")) return(market)}get.

markets <- function ( agora.

api, auth.

token, partition.

id ) { markets <- fromJSON(content(GET(url=sprintf("%s/%s/markets",agora.

api,partition.

id),accept_json(),add_headers(Authorization=auth.

token)),as="text")) return(markets)}get.

variable <- function( agora.

api, auth.

token, partition.

id, variable.

id ) { variable <- fromJSON(content(GET(url=sprintf("%s/%s/variables/%s",agora.

api,partition.

id,variable.

id),accept_json(),add_headers(Authorization=auth.

token)),as="text")) if (variable$type=="Continuous") { events <- 0.

5*(variable$events$low$value + variable$events$high$value) variable$midpoint <- events } else { events <- variable$events$value variable$midpoint <- events } return(variable)}get.

prices <- function( agora.

api, auth.

token, partition.

id, market.

id ) { prices <- fromJSON(content(GET(url=sprintf("%s/%s/markets/%s/prices",agora.

api,partition.

id,market.

id),accept_json(),add_headers(Authorization=auth.

token)),as="text")) return(prices)}create.

contract <- function( agora.

api, auth.

token, partition.

id, market.

id, name, weights ) { body <- list( marketId=unbox(market.

id), name=unbox(name), weights=weights ) json <- toJSON(body) post <- POST(url=sprintf("%s/%s/contracts",agora.

api,partition.

id),add_headers(Authorization=auth.

token),body=json,encode="raw",verbose(),content_type_json()) contract.

id <- fromJSON(content(post,as="text")) return(contract.

id)}get.

quote <- function( agora.

api, auth.

token, partition.

id, contract.

id, quantity ) { n.

contracts <- length(contract.

id) body <- list() for (i.

contract in c(1:n.

contracts)) { body[[i.

contract]] <- list( contractId=unbox(contract.

id[i.

contract]), quantity=unbox(quantity[i.

contract]) ) } json <- toJSON(body) post <- POST(url=sprintf("%s/%s/quotes",agora.

api,partition.

id),add_headers(Authorization=auth.

token),body=json,encode="raw",verbose(),content_type_json()) quote <- fromJSON(content(post,as="text")) return(quote)}create.

realtime.

order <- function( agora.

api, auth.

token, partition.

id, contract.

id, quantity, limit ) { n.

contracts <- length(contract.

id) basket <- list() for (i.

contract in c(1:n.

contracts)) { basket[[i.

contract]] <- list( contractId=unbox(contract.

id[i.

contract]), quantity=unbox(quantity[i.

contract]) ) } body <- list(contractQuantities=basket,limit=unbox(limit)) json <- toJSON(body) post <- POST(url=sprintf("%s/%s/orders/real-time",agora.

api,partition.

id),add_headers(Authorization=auth.

token),body=json,encode="raw",verbose(),content_type_json()) realtime.

order <- fromJSON(content(post,as="text")) return(realtime.

order)}get.

my.

accounts <- function( agora.

api, auth.

token, partition.

id ) { accounts <- fromJSON(content(GET(url=sprintf("%s/%s/accounts/me",agora.

api,partition.

id),accept_json(),add_headers(Authorization=auth.

token)),as="text")) return(accounts)}Get an authentication tokenFirst we must set a couple of variables specifying URLs and get an authentication token for the API:auth.

api <- 'https://auth.

lambda.

hvmd.

io'agora.

api <- 'https://agora-api.

azurewebsites.

net'auth.

token <- get.

authentication.

token( auth.

api, email="me@myemail", password="mypassword" )Get a list of all markets in the Agora Climate partitionpartition.

id <- 'd236c9fd-b77e-47f7-b17a-40786831c78f'markets <- get.

markets( agora.

api, auth.

token, partition.

id )We can list the attributes describing each market:names(markets)## [1] "marketSpaceId" "marketGroupId" "name" ## [4] "description" "variableIds" "instructions" ## [7] "parameters" "qualificationIds" "settled" ## [10] "partitionId" "id" "_basetype" ## [13] "_type"We can then choose to list a couple of these attributes for each market:markets[,c('name','variableIds')]## name variableIds## 1 NINO3.

4SSTA (JUL 2019) a0dc36ae-859f-4a75-925d-e00094beb8e0## 2 NINO3.

4SSTA (AUG 2019) a0dc36ae-859f-4a75-925d-e00094beb8e0## 3 NINO3.

4SSTA (SEP 2019) a0dc36ae-859f-4a75-925d-e00094beb8e0## 4 NINO3.

4SSTA (OCT 2019) a0dc36ae-859f-4a75-925d-e00094beb8e0## 5 NINO3.

4SSTA (NOV 2019) a0dc36ae-859f-4a75-925d-e00094beb8e0## 6 NINO3.

4SSTA (DEC 2019) a0dc36ae-859f-4a75-925d-e00094beb8e0## 7 NINO3.

4SSTA (JAN 2020) a0dc36ae-859f-4a75-925d-e00094beb8e0## 8 NINO3.

4SSTA (FEB 2020) a0dc36ae-859f-4a75-925d-e00094beb8e0## 9 NINO3.

4SSTA (MAR 2020) a0dc36ae-859f-4a75-925d-e00094beb8e0Variable informationNotice that all the markets use the same variable.

We can obtain more information about this variable.

variable.

id <- 'a0dc36ae-859f-4a75-925d-e00094beb8e0'variable <- get.

variable( agora.

api, auth.

token, partition.

id, variable.

id )The attributes describing this variable are:names(variable)## [1] "events" "unit" "description" ## [4] "type" "_type" "name" ## [7] "shortName" "qualificationIds" "partitionId" ## [10] "id" "_basetype" "midpoint"The midpoint attribute isn’t an intrinsic attribute but is calculated from the events information by the wrapper function.

variable$midpoint## [1] NA -3.

95 -3.

85 -3.

75 -3.

65 -3.

55 -3.

45 -3.

35 -3.

25 -3.

15 -3.

05## [12] -2.

95 -2.

85 -2.

75 -2.

65 -2.

55 -2.

45 -2.

35 -2.

25 -2.

15 -2.

05 -1.

95## [23] -1.

85 -1.

75 -1.

65 -1.

55 -1.

45 -1.

35 -1.

25 -1.

15 -1.

05 -0.

95 -0.

85## [34] -0.

75 -0.

65 -0.

55 -0.

45 -0.

35 -0.

25 -0.

15 -0.

05 0.

05 0.

15 0.

25## [45] 0.

35 0.

45 0.

55 0.

65 0.

75 0.

85 0.

95 1.

05 1.

15 1.

25 1.

35## [56] 1.

45 1.

55 1.

65 1.

75 1.

85 1.

95 2.

05 2.

15 2.

25 2.

35 2.

45## [67] 2.

55 2.

65 2.

75 2.

85 2.

95 3.

05 3.

15 3.

25 3.

35 3.

45 3.

55## [78] 3.

65 3.

75 3.

85 3.

95 NAThese are the midpoints of the intervals of the outcome space.

The first and last intervals are NAs because they are open intervals that extend to minus and plus infinity respectively.

Getting current pricesTo get the current prices for a specific monthly market or to place trades in it we will need the ID.

We can get the market IDs from the markets object that we obtained earlier:markets[,c('name','id')]## name id## 1 NINO3.

4SSTA (JUL 2019) 738091ce-d0ad-4158-bde7-a928c9421396## 2 NINO3.

4SSTA (AUG 2019) 9a84195b-b737-4a99-96d8-34057a5b28ed## 3 NINO3.

4SSTA (SEP 2019) 81a980e7-d0ed-4ad6-9bbf-0f6aba9b136d## 4 NINO3.

4SSTA (OCT 2019) 993046ba-1f7f-4437-bc07-9ec34c3473c3## 5 NINO3.

4SSTA (NOV 2019) 45a8fc27-8fb2-4075-b065-16d7182702a0## 6 NINO3.

4SSTA (DEC 2019) 0d2a2bb2-a9a3-40fa-8a30-d12a1b422192## 7 NINO3.

4SSTA (JAN 2020) d3fc7d9b-2ba3-410f-a541-0435413e97d1## 8 NINO3.

4SSTA (FEB 2020) e110d148-074c-4453-b332-ab40a7808a51## 9 NINO3.

4SSTA (MAR 2020) 5cbc450b-253f-451e-936c-60b78755a97fLet’s get the prices for the March 2020 market and plot them:market.

id <- markets[9,]$id ### ID for March 2020 marketprices <- get.

prices( agora.

api, auth.

token, partition.

id, market.

id )plot( na.

spline(variable$midpoint), prices$value, type='l', xlab='NINO3.

4 SSTA (u00b0C)', ylab='price' )We’ve used the na.

spline functionto extrapolate values for the first and last midpoints.

The current prices obtained via the API plotted against the mid-point values of each NINO3.

4 SSTA interval.

Creating contractsNext we’ll create a contract.

This contract will be for an El Niño event and will have a weight of 1.

00 for every outcome above 2.

0 deg.

C.

To do this we need to define a contract.

weight object which has the same structure as the prices object except the value attribute specifies the weight on each outcome rather than the price.

contract.

weight <- pricescontract.

weight$value <- as.

numeric(na.

spline(variable$midpoint) >= 2.

05) contract.

name <- 'El Nino Event'contract.

id <- create.

contract( agora.

api, auth.

token, partition.

id, market.

id, contract.

name, contract.

weight )contract.

id## [1] "a23eb5e0-3752-4e2c-9de3-3ad7f7e82d44"The weight on each outcome is the number of credits we will receive if that outcome occurs and we own 1.

00 unit of the contract.

The creation of contracts is one area where the Agora API offers more flexibility than the Agora UI: In the UI, contract weights on each outcome must be either 0 or 1 whereas, when creating a contract with the API, the weights can be arbitrary (non-negative) values.

To illustrate this we will create another contract with the shape of a gaussian distribution with mean 1.

00 and standard deviation 0.

5.

We will normalize the weights so that the largest weight is equal to 1.

00, but this is an arbitrary choice.

contract.

weight <- pricescontract.

weight$value <- dnorm(na.

spline(variable$midpoint),1.

00,0.

5)contract.

weight$value <- contract.

weight$value/max(contract.

weight$value)contract.

name <- '1.

0C +/- 0.

5C'contract.

id2 <- create.

contract( agora.

api, auth.

token, partition.

id, market.

id, contract.

name, contract.

weight )contract.

id2## [1] "084084d7-95db-414e-bcdf-b144009c6129"Trading contractsBoth of the contracts we have created are visible from the Agora UI.

Once created we can trade them via the UI as discussed in the previous blog but they can also be traded using the API.

Before placing a trade it is often helpful to get a quote.

Let’s get a quote for 10 units of the El Niño contract we created.

quantity <- 10quote <- get.

quote( agora.

api, auth.

token, partition.

id, contract.

id, quantity )quote## $spread## [1] 0.

0001269025## ## $value## [1] 0.

2690249The quote$value is the value of the quote while quote$spread is the spread that we must pay on top of the value.

We can now place a trade.

The only additional piece of information we need for this is a limit.

This is the maximum price we are prepared to pay.

We will make this limit equal to the price quoted (value plus spread).

limit <- quote$value + quote$spreadtrade1 <- create.

realtime.

order( agora.

api, auth.

token, partition.

id, contract.

id, quantity, limit )For sell trades we would just use a negative value for quantity.

As with the Agora UI we can place basket trades in which we bundle together transactions for different contracts into a single trade.

Let’s illustrate this by selling 5 units of the contract we’ve just bought while simultaneously purchasing 7 units of the other contract we created earlier:contracts <- c(contract.

id,contract.

id2)quantities <- c(-5,+7)quote2 <- get.

quote( agora.

api, auth.

token, partition.

id, contracts, quantities )limit2 <- quote2$value + quote2$spreadtrade2 <- create.

realtime.

order( agora.

api, auth.

token, partition.

id, contracts, quantities, limit2)The contracts we created using the API can also be traded using the Agora UI.

Getting account balancesWe can get our account balances using get.

my.

accounts.

Each account corresponds to a marketSpaceId and we can use this ID to get the name of the market from the markets object created earlier.

accounts <- get.

my.

accounts( agora.

api, auth.

token, partition.

id )accounts$marketname <- markets$name[match(accounts$marketSpaceId,markets$marketSpaceId)]accounts[,c('marketname','balance')]## marketname balance## 1 NINO3.

4SSTA (JUL 2019) 500.

0000## 2 NINO3.

4SSTA (AUG 2019) 499.

7308## 3 NINO3.

4SSTA (SEP 2019) 500.

0000## 4 NINO3.

4SSTA (OCT 2019) 500.

0000## 5 NINO3.

4SSTA (NOV 2019) 500.

0000## 6 NINO3.

4SSTA (DEC 2019) 500.

0000## 7 NINO3.

4SSTA (JAN 2020) 500.

0000## 8 NINO3.

4SSTA (FEB 2020) 500.

0000## 9 NINO3.

4SSTA (MAR 2020) 498.

0378Constructing a trade from a probability distributionIf we have our own view of the probability distribution of NINO3.

4 SSTA for a particular month and this distribution differs from the distribution of current prices then what trade could we make to exploit this difference?The function create.

optimal.

order below addresses this question.

It determines the trade that will move the current prices to match our probability distribution.

If the cost of this trade exceeds our specified budget it performs a line search to find a reduced version of the order that satisfies our budget constraint.

This reduced version of the trade prioritizes the outcomes which are most mispriced according to our own probability distribution.

create.

optimal.

order <- function( agora.

api, auth.

token, partition.

id, market.

id, my.

probability, my.

budget, name="MY CONTRACT", tol=1e-3, allowable.

slippage=0.

001, place.

order=TRUE ) { prices <- get.

prices( agora.

api, auth.

token, partition.

id, market.

id ) market <- get.

market( agora.

api, auth.

token, partition.

id, market.

id ) b <- market$parameters$liquidityFactor prob <- my.

probability[match(prices$eventIds,my.

probability$eventIds),]$value W <- b*log(prob/sum(prob)) – b*log(prices$value) W <- W – min(W) q <- b*log(prices$value) – min(b*log(prices$value)) costfunc <- function( x ) { w1 <- W – x w1[w1 < 0] <- 0 P <- b*log(sum(exp((q+w1)/b))) – b*log(sum(exp(q/b))) ### quote value S <- market$parameters$spreadCoefficients$constant + P*market$parameters$spreadCoefficients$factor ### quote spread return(P+S-my.

budget) } if (costfunc(0) < 0) { ### entire order is within budget x <- 0 } else { ### determine what reduced version is within budget x <- uniroot(costfunc,c(0,max(W)),tol=tol)$root } w1 <- W – x w1[w1 < 0] <- 0 ### create contract weights <- prices quantity <- 1/tol weights$value <- w1/(quantity+1) ### define contract such that entire order is 1/tol and stay within budget if (place.

order) { contract.

id <- create.

contract( agora.

api, auth.

token, partition.

id, market.

id, name, weights ) quote <- get.

quote( agora.

api, auth.

token, partition.

id, contract.

id, quantity ) if (quote$value + quote$spread <= my.

budget) { ### go ahead and place order limit <- (quote$value + quote$spread)*(1.

0 + allowable.

slippage) order.

id <- create.

realtime.

order( agora.

api, auth.

token, partition.

id, contract.

id, quantity, limit ) } } else { contract.

id <- NA } return( list(quote=quote,quantity=quantity,weights=weights,contract.

id=contract.

id) )}Let’s suppose our own probability distribution is a gaussian centered on -1.

00 with a standard deviation 0.

5.

We can pass this probability to thecreate.

optimal.

order function along with our budget which is the maximum number of credits we are willing to spend on this trade.

The function will determine the optimal trade for our probability distribution and budget and if place.

order=TRUE place the trade.

The contract weights will be normalized such that the total trade consists of 1/tol units.

my.

probability <- pricesmy.

probability$value <- dnorm(na.

spline(variable$midpoint),-1.

00,0.

5)my.

probability$value <- my.

probability$value/sum(my.

probability$value)my.

budget <- 400trade3 <- create.

optimal.

order( agora.

api, auth.

token, partition.

id, market.

id, my.

probability, my.

budget, 'optimal contract', tol=1e-2, place.

order=TRUE )The weights associated with the optimal contract.

Once the contracted has been created we can see it via the UI.

Since 1/tol was set at 100 that is how many units of the contracts have been bought.

This trade was within the budget we specified of 400 credits.

SummaryThe Agora API allows programmatic trading in the Hivemind El Niño Prediction Market.

It can be used to automate trading and makes the integration of the output of predictive models into trading decisions easier.

Further details are available on the Agora Swagger documentation.

Click AUTHORIZE in the top right and then use your authentication token (obtained using the function described above) in the first Authorization field and the partition id of d236c9fd-b77e-47f7-b17a-40786831c78f in the Partition field.

You can also obtain your authentication token by opening Developer Tools in your browser and local in the local storage having already logged into the agora.

lambda.

hvmd.

io site.

You then enter “Bearer TOKEN” in the first authentication field.

.

. More details

Leave a Reply