Building a stock market monitoring application
Our stock market program will consist of three main components:
- A function simulating an external service from which we can query the current price—this would likely be a network call in a real setting
- A scheduler that polls the preceding function at a predefined interval
- A display function that's responsible for updating the screen
We'll start by creating a new Leiningen project, where the source code for our application will live. Type the following on the command line and then switch into the newly created directory:
lein new stock-market-monitor cd stock-market-monitor
As we'll be building a GUI for this application, go ahead and add a dependency on seesaw to the dependencies section of your project.clj:
[seesaw "1.5.0"]
Next, create a src/stock_market_monitor/core.clj file in your favorite editor. Let's create and configure our application's UI components:
(ns stock-market-monitor.core (:require [seesaw.core :refer :all]) (:import (java.util.concurrent ScheduledThreadPoolExecutor TimeUnit))) (native!) (def main-frame (frame :title "Stock price monitor" :width 200 :height 100 :on-close :exit)) (def price-label (label "Price: -")) (config! main-frame :content price-label)
As you can see, the UI is fairly simple. It consists of a single label that will display a company's share price. We also imported two Java classes, ScheduledThreadPoolExecutor and TimeUnit, which we will use shortly.
The next thing we need is our polling machinery so that we can invoke the pricing service on a given schedule. We'll implement this via a thread pool so as not to block the main thread:
(def pool (atom nil)) (defn init-scheduler [num-threads] (reset! pool (ScheduledThreadPoolExecutor. num-threads))) (defn run-every [pool millis f] (.scheduleWithFixedDelay pool f 0 millis TimeUnit/MILLISECONDS)) (defn shutdown [pool] (println "Shutting down scheduler...") (.shutdown pool))
The init-scheduler function creates ScheduledThreadPoolExecutor with the given number of threads. That's the thread pool in which our periodic function will run. The run-every function schedules a function, f, in the given pool to run at the interval specified by millis. Finally, shutdown is a function that will be called on program termination and thus will shut down the thread pool gracefully.
The rest of the program puts all of these parts together:
(defn share-price [company-code] (Thread/sleep 200) (rand-int 1000)) (defn -main [& args] (show! main-frame) (.addShutdownHook (Runtime/getRuntime) (Thread. #(shutdown @pool))) (init-scheduler 1) (run-every @pool 500 #(->> (str "Price: " (share-price "XYZ")) (text! price-label) invoke-now)))
The share-price function sleeps for 200 milliseconds to simulate network latency and returns a random integer between 0 and 1,000, representing the stock's price.
The second line of our -main function adds a shutdown hook to the runtime. This allows our program to intercept termination, such as pressing Ctrl + C in a Terminal window, and gives us the opportunity to shut down the thread pool.
Next, we initialize the scheduler with a single thread and schedule a function to be executed every 500 milliseconds. This function asks the share-price function for XYZ's current price and updates the label.
Let's run the program by typing the following command in the project's root directory:
lein trampoline run -m stock-market-monitor.core
A window like the one shown in the following screenshot will be displayed, with the values on it being updated as per the schedule that we implemented earlier:
This is a fine solution. The code is relatively straightforward and satisfies our original requirements. However, if we look at the big picture, there is a fair bit of noise in our program. Most of its lines of code are dealing with creating and managing a thread pool, which, while necessary, isn't central to the problem we're solving—it's an implementation detail.
We'll keep things as they are for the moment and add a new requirement: rolling averages.