If you are reading this page you probably know that ThetaData is a market data (e.g. stocks, options) vendor.

You interact with ThetaData via a REST API (or a WebSocket) but you need to be running the server locally. The reason being that between your local server and ThetaData data centers they use a proprietary protocol to speed up data transfers.

So there is an extra layer of complexity compared to “pure REST API” vendors to make queries faster. But is it really faster ? If you are pulling large chuncks of data such as the option chain (all expiration dates and strikes) of a liquid stock like Apple yes it feels much faster than the other vendors I tried.

On the website you will find instructions to download the local server (java) and to run it.

Our goal is to run ThetaData in the cloud to feed our batch jobs.

First we will package the ThetaData java server into a Docker image.

Then we will create a Kubernetes cluster and deploy an app based on the previous Docker image.

Docker image

We start with the official documentation on how to install and run the server:

We use these commands to write a Docker file and create an Docker image:

Dockerfile

    
FROM openjdk:17-slim

# Install wget
RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/*

# Create the target directory
RUN mkdir /etc/thetadata

# Download the JAR file
RUN wget https://download-latest.thetadata.us -O /etc/thetadata/ThetaTerminal.jar

# Set the working directory
WORKDIR /etc/thetadata

# Expose the port (optional)
EXPOSE 25510

CMD java -jar ThetaTerminal.jar $EMAIL_ADDRESS $PASSWORD

To run the image locally:

docker run --name my-java-container -e EMAIL_ADDRESS='*********' -e PASSWORD='********' -p 25510:25510 theta-app

Sample query to test it is running fine:

curl --request GET \                          
  --url 'http://127.0.0.1:25510/v2/hist/stock/eod?root=AAPL&start_date=20240102&end_date=20240102' \                   
  --header 'Accept: application/json'
Some JSON payload with open/high/low/close prices

Kubernetes cluster on GKE (Google Cloud)

First create a cluster. We use only one node as ThetaData only allows for one connection per account. Remark: The machine spec may need to be reviewed after experimenting with it.

gcloud container clusters create thetadata-cluster \
  --zone europe-west1-b \
  --num-nodes=1 \
  --machine-type=e2-standard-4 --disk-size=25

This can take a couple minutes to complete.

Then get the cluster credentials so that your local terminal can interact with it:

gcloud container clusters get-credentials thetadata-cluster --zone=europe-west1-b
Fetching cluster endpoint and auth data.
kubeconfig entry generated for thetadata-cluster.

Deploy the app

Our Docker image requires the ThetaData login credentials to start so we create a kubernetes secret to store them:

kubectl create secret generic thetadata-credentials \
  --from-literal=EMAIL_ADDRESS='YOUR_LOGIN_EMAIL' \
  --from-literal=PASSWORD='YOUR_LOGIN_PASSWORD'
secret/thetadata-credentials created

Then we write our Kubernetes deployment file:

deployment.yaml

    
apiVersion: apps/v1
kind: Deployment
metadata:
  name: thetadata-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: thetadata
  template:
    metadata:
      labels:
        app: thetadata
    spec:
      containers:
      - name: thetadata
        image: YOUR_DOCKER_IMAGE_REPO/theta-server
        imagePullPolicy: Always
        ports:
        - containerPort: 25510
        env:
        - name: EMAIL_ADDRESS
          valueFrom:
            secretKeyRef:
              name: thetadata-credentials
              key: EMAIL_ADDRESS
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: thetadata-credentials
              key: PASSWORD
Note you need to fill in place the path to your Docker image registry.

We can now deploy the app using the Docker image we created earlier:

kubectl apply -f deployment.yaml
deployment.apps/thetadata-app configured

Testing

We are going to port-forward our local requests into the app.

First find the pod where the app is running:

kubectl get pods
NAMEREADYSTATUSRESTARTSAGE
thetadata-app-4f475t55b6-cxw651/1Running019m

Check the logs in the pod, this will allow you to confirm your pod connects fine with ThetaData servers and your login information are correct. You should see the following at start up:

kubectl logs thetadata-app-4f475t55b6-cxw65
[02-28-2025 10:50:02] INFO: Starting Theta Terminal v1.8.4 Revision A...
[02-28-2025 10:50:02] INFO: Using /root/ThetaData/ThetaTerminal/logs as the log directory
[02-28-2025 10:50:02] INFO: Using /root/ThetaData/ThetaTerminal/config_0.properties as the config file
[02-28-2025 10:50:02] WARN: Configuration file not found: created a default config file.
[02-28-2025 10:50:03] INFO: [MDDS] Attempting login as YOUR_LOGIN_EMAIL
[02-28-2025 10:50:04] INFO: [FPSS] Attempting login as YOUR_LOGIN_EMAIL
[02-28-2025 10:50:05] INFO: [MDDS] CONNECTED: [nj-a.thetadata.us:12000], Bundle: STOCK.VALUE, OPTION.VALUE, INDEX.VALUE

Then port-forward with:

kubectl port-forward pod/thetadata-app-4f475t55b6-cxw65 25510:25510 
Forwarding from 127.0.0.1:25510 -> 25510
Forwarding from [::1]:25510 -> 25510

Finaly send the same sample query as before to test it is running fine:

curl --request GET \
  --url 'http://127.0.0.1:25510/v2/hist/stock/eod?root=AAPL&start_date=20240102&end_date=20240102' \
  --header 'Accept: application/json'
Some JSON payload with open/high/low/close prices

Create a service

Once our kubernetes app is working fine we want to make it easier to interact with it. For this we need a kubernetes service which we create by applying the following service.yaml file:

service.yaml

    
apiVersion: v1
kind: Service
metadata:
  name: thetadata-service
spec:
  selector:
    app: thetadata
  ports:
    - protocol: TCP
      port: 25510
      targetPort: 25510
  type: ClusterIP

kubectl apply -f service.yaml
service/thetadata-service created

Check the service is running

kubectl get svc
NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE
kubernetesClusterIP34.118.224.1none443/TCP108m
thetadata-serviceClusterIP34.118.235.87none25510/TCP7s

Then port-forward with:

kubectl port-forward svc/thetadata-service 25510:25510
Forwarding from 127.0.0.1:25510 -> 25510
Forwarding from [::1]:25510 -> 25510