├── .lein-plugins └── checksum ├── .travis.yml ├── .gitignore ├── examples ├── security-lookup.clj ├── getting-news.clj ├── getting-fundamentals.clj ├── historical-data-request.clj ├── gap-fade.clj └── tick-recorder.clj ├── src └── ib_re_actor │ ├── positions.clj │ ├── account.clj │ ├── util.clj │ ├── synchronous.clj │ ├── mapping.clj │ ├── gateway.clj │ ├── wrapper.clj │ ├── client_socket.clj │ └── translation.clj ├── project.clj ├── scripts └── twsapi-971 ├── test └── ib_re_actor │ └── test │ ├── translation.clj │ ├── mapping.clj │ └── wrapper.clj └── README.md /.lein-plugins/checksum: -------------------------------------------------------------------------------- 1 | acef128a22eb53a862605d3745d24837dce38b67 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | script: lein2 midje -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | *.jar 7 | *.class 8 | .lein-deps-sum 9 | .lein-failures 10 | .lein-plugins 11 | /errors.log 12 | /ticks.csv 13 | /.nrepl-port 14 | -------------------------------------------------------------------------------- /examples/security-lookup.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.examples.security-lookup 2 | (:use [ib-re-actor.securities] 3 | [clj-time.core :only [date-time]] 4 | [clojure.pprint :only [pprint]])) 5 | 6 | (defn print-contracts-details [contract-details] 7 | (pprint contract-details)) 8 | 9 | (-> (lookup-security {:security-type :future :symbol "ES" :exchange "GLOBEX"}) 10 | print-contracts-details) -------------------------------------------------------------------------------- /src/ib_re_actor/positions.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.positions) 2 | 3 | (defonce positions (atom nil)) 4 | 5 | (defn handle-portfolio-update [{:keys [type contract] :as msg}] 6 | (when (= type :update-portfolio) 7 | (swap! positions assoc contract 8 | (select-keys msg [:position :market-price :market-value 9 | :average-cost :unrealized-gain-loss 10 | :realized-gain-loss])))) 11 | 12 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject ib-re-actor "0.2.0-SNAPSHOT" 2 | :description "Clojure friendly wrapper for InteractiveBrokers java API" 3 | :dependencies [[org.clojure/clojure "1.9.0"] 4 | [clj-time "0.14.2"] 5 | [org.clojure/tools.logging "0.4.0"] 6 | [clj-logging-config "1.9.12"]] 7 | :profiles {:dev {:dependencies [[twsapi "9.71.01"] 8 | [midje "1.9.1"]] 9 | :plugins [[lein-midje "3.2.1"] 10 | [com.gfredericks/how-to-ns "0.1.6"]] 11 | :how-to-ns {:require-docstring? false}}}) 12 | -------------------------------------------------------------------------------- /src/ib_re_actor/account.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.account 2 | " Account details 3 | This namespace deals monitoring and updating account details.") 4 | 5 | ;; This atom contains the account details at all times 6 | (defonce account-details (atom nil)) 7 | 8 | (defn update-account-details 9 | "Update a partical key in the account details" 10 | [{:keys [type key value currency]}] 11 | (case type 12 | :update-account-value 13 | (swap! account-details assoc key 14 | (if (nil? currency) 15 | value 16 | (vector value currency))) 17 | 18 | :update-account-time 19 | (swap! account-details assoc :last-updated value))) 20 | -------------------------------------------------------------------------------- /examples/getting-news.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.examples.getting-news 2 | (:use [ib-re-actor.connection])) 3 | 4 | (def news-complete? (atom false)) 5 | (defmulti news-handler :type) 6 | (defmethod news-handler :error [msg] 7 | (cond 8 | (contains? msg :message) (println "*** " (:message msg)) 9 | (contains? msg :exception) (println "*** " (.toString (:exception msg)))) 10 | (if (not (warning? msg)) 11 | (reset! news-complete? true))) 12 | (defmethod news-handler :default [msg] (prn msg)) 13 | (defmethod news-handler :update-news-bulletin [{message :message}] 14 | (println message)) 15 | 16 | (defn watch-news [] 17 | (let [connection (connect news-handler)] 18 | (try 19 | (reset! news-complete? false) 20 | (request-news-bulletins connection) 21 | (while (not @news-complete?) 22 | (java.lang.Thread/sleep 100)) 23 | (finally (disconnect connection))))) 24 | 25 | -------------------------------------------------------------------------------- /scripts/twsapi-971: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pushd $( pwd ) > /dev/null 4 | 5 | if [ "$#" -ne 1 ]; then 6 | echo "Please provide jar archive of twsapi as argument." 7 | fi 8 | 9 | TMP_DIR=$( mktemp -d ) 10 | 11 | if [[ ! "$TMP_DIR" || ! -d "$TMP_DIR" ]]; then 12 | echo "Could not create temporary directory." 13 | exit 1 14 | fi 15 | 16 | echo "Created temporary working directory $TMP_DIR" 17 | 18 | function cleanup { 19 | rm -rf "$TMP_DIR" 20 | echo "Deleted temporary working directory." 21 | popd > /dev/null 22 | } 23 | 24 | trap cleanup EXIT 25 | 26 | echo "Extracting zip archive..." 27 | unzip -q $1 -d $TMP_DIR 28 | 29 | VERSION=$( echo $1 | sed 's/.*\.\([0-9]\)\([0-9][0-9]\)\.\([0-9][0-9]\)\.jar/\1.\2.\3/' ) 30 | 31 | pushd $( pwd ) > /dev/null 32 | cd $TMP_DIR/IBJts/source/JavaClient 33 | mkdir bin 34 | javac -d bin -sourcepath . com/ib/*/*.java 35 | ant 36 | popd > /dev/null 37 | 38 | echo "Installing twsapi $VERSION..." 39 | lein localrepo install $TMP_DIR/IBJts/javaclient.jar twsapi $VERSION 40 | -------------------------------------------------------------------------------- /examples/getting-fundamentals.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.examples.getting-fundamentals 2 | (:use [ib-re-actor.connection] 3 | [ib-re-actor.contracts] 4 | [clj-time.core :only [date-time minus]])) 5 | 6 | (def fundamental-data-complete? (atom false)) 7 | (defmulti fundamental-data-handler :type) 8 | (defmethod fundamental-data-handler :error [msg] 9 | (cond 10 | (contains? msg :message) (println "*** " (:message msg)) 11 | (contains? msg :exception) (println "*** " (.toString (:exception msg)))) 12 | (if (not (warning? msg)) 13 | (reset! fundamental-data-complete? true))) 14 | (defmethod fundamental-data-handler :default [msg] (prn msg)) 15 | (defmethod fundamental-data-handler :fundamental-data [{report :report}] 16 | (prn report) 17 | (reset! fundamental-data-complete? true)) 18 | 19 | (defn get-fundamentals [] 20 | (let [connection (connect fundamental-data-handler)] 21 | (try 22 | (reset! fundamental-data-complete? false) 23 | (request-fundamental-data connection -1 (equity "C" "NYSE") :summary) 24 | (while (not @fundamental-data-complete?) 25 | (java.lang.Thread/sleep 100)) 26 | (finally (disconnect connection))))) 27 | -------------------------------------------------------------------------------- /src/ib_re_actor/util.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.util 2 | (:require 3 | [ib-re-actor.translation :as t])) 4 | 5 | (def ^:const option-flag-keywords #{:read-only}) 6 | 7 | (defmacro field-props [& property-descriptors] 8 | (reduce (fn [implementation [name field & options]] 9 | (let [option-flags (set (take-while option-flag-keywords options)) 10 | kw-args (drop-while option-flag-keywords options) 11 | {:keys [translation]} (apply hash-map kw-args) 12 | this (gensym "this") 13 | val (gensym "val")] 14 | (assoc implementation (keyword name) 15 | `(fn 16 | ([~this] 17 | ~(if translation 18 | `(t/translate :from-ib ~translation (. ~this ~field)) 19 | `(. ~this ~field))) 20 | ~@(if (not (option-flags :read-only)) 21 | `(([~this ~val] 22 | (set! (. ~this ~field) 23 | ~(if translation 24 | `(t/translate :to-ib ~translation ~val) 25 | val)) 26 | ~this))))))) 27 | {} property-descriptors)) 28 | 29 | (defn assoc-if [map key val] 30 | (if val 31 | (assoc map key val) 32 | map)) 33 | -------------------------------------------------------------------------------- /examples/historical-data-request.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.examples.historical-data-request 2 | (:require [clojure.string :as s] 3 | [ib-re-actor.gateway :as g]) 4 | (:use [ib-re-actor.synchronous :only [get-historical-data]] 5 | [clj-time.core :only [date-time minus plus hours minutes 6 | year month day before?]] 7 | [clojure.tools.logging :only [error debug]])) 8 | 9 | (defn end-times [start end t] 10 | (letfn [(add-t [x] (plus x t))] 11 | (->> (iterate add-t start) 12 | (take-while #(before? % end)) 13 | (map add-t)))) 14 | 15 | (defn midnight-on [d] 16 | (date-time (year d) (month d) (day d))) 17 | 18 | (defn get-daily-data 19 | [contract date] 20 | (let [req-id (g/get-request-id) 21 | midnight (midnight-on date) 22 | end (plus midnight (hours 20) (minutes 15)) 23 | start (plus midnight (hours 11) (minutes 30)) 24 | end-times (agent (end-times start end (minutes 30))) 25 | all-complete (promise) 26 | make-request (fn [[end-time & next-end-times]] 27 | (if end-time 28 | (do 29 | (debug "Requesting up to " end-time) 30 | (g/request-historical-data req-id contract end-time 31 | 30 :minutes 1 :second 32 | :trades false)) 33 | (do 34 | (debug "all done") 35 | (deliver all-complete true))) 36 | next-end-times) 37 | send-request (fn [] 38 | (debug "Delaying request 15 seconds...") 39 | (future 40 | (do (Thread/sleep 15000) 41 | (debug "delay over") 42 | (send end-times make-request)))) 43 | handler (fn [{:keys [type request-id] :as msg}] 44 | (when (= req-id request-id) 45 | (case type 46 | :price-bar 47 | (->> (map msg [:time :open :high :low :close 48 | :volume :trade-count :has-gaps?]) 49 | (s/join ",") 50 | println) 51 | 52 | :price-bar-complete 53 | (send-request) 54 | 55 | :otherwise 56 | (debug msg))))] 57 | (g/subscribe handler) 58 | (try 59 | (send end-times make-request) 60 | @all-complete 61 | (finally 62 | (g/unsubscribe handler))))) 63 | 64 | (comment 65 | (require 'clj-logging-config.log4j) 66 | (clj-logging-config.log4j/set-logger! :level :debug)) -------------------------------------------------------------------------------- /examples/gap-fade.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.examples.gap-fade 2 | (:use [ib-re-actor.connection] 3 | [ib-re-actor.contracts] 4 | [clj-time.core :only [date-time minus]])) 5 | 6 | (defn compute-pivots [{high :high low :low close :close}] 7 | (let [pp (/ (+ high low close) 3) 8 | r1 (- (* 2 pp) low) 9 | s1 (- (* 2 pp) high) 10 | r2 (+ pp (- r1 s1)) 11 | s2 (- pp (- r1 s1)) 12 | r3 (+ high (* 2 (- pp low))) 13 | s3 (- low (* 2 (- high pp)))] 14 | {:high high :low low :close close 15 | :r3 r3 :r2 r2 :r1 r1 16 | :pp pp 17 | :s1 s1 :s2 s2 :s3 s3})) 18 | 19 | (def historic-prices (atom {})) 20 | 21 | (defmulti historic-handler :type) 22 | (defmethod historic-handler :error [msg] 23 | (cond 24 | (contains? msg :message) (println "*** " (:message msg)) 25 | (contains? msg :exception) (println "*** " (.toString (:exception msg)))) 26 | (if (not (warning? msg)) 27 | (swap! historic-prices assoc :done? true :failed? true))) 28 | 29 | (defmethod historic-handler :price-bar [msg] 30 | (swap! historic-prices assoc 31 | :bars (conj (:bars @historic-prices) 32 | {:time (:time msg) 33 | :high (:high msg) 34 | :low (:low msg) 35 | :close (:close msg)}))) 36 | 37 | (defmethod historic-handler :complete [msg] 38 | (swap! historic-prices assoc :done? true)) 39 | 40 | (defmethod historic-handler :default [msg] 41 | (prn msg)) 42 | 43 | (defn compute-daily-pivots [contract date] 44 | (let [connection (connect historic-handler)] 45 | (try 46 | (reset! historic-prices {}) 47 | (request-historical-data connection 1 contract date 1 :day 1 :day :trades) 48 | (while (not (:done? @historic-prices)) 49 | (print ".") 50 | (java.lang.Thread/sleep 250)) 51 | (if (:failed? @historic-prices) 52 | (println "*** failed to get historic prices") 53 | (compute-pivots (first (:bars @historic-prices)))) 54 | (finally 55 | (disconnect connection))))) 56 | 57 | (defmulti tick-handler :type) 58 | 59 | (defmethod tick-handler :error [msg] 60 | (cond 61 | (contains? msg :message) (println "*** " (:message msg)) 62 | (contains? msg :exception) (println "*** " (.toString (:exception msg)))) 63 | (if (not (warning? msg)) 64 | (swap! historic-prices assoc :done? true :failed? true))) 65 | 66 | (def current-prices (atom {})) 67 | (defmethod tick-handler :price-tick [{ticker-id :ticker-id field :field price :price}] 68 | (swap! current-prices assoc ticker-id {})) 69 | (defmethod tick-handler :price-tick :bid-size [{price :price}]) 70 | (defmethod tick-handler :price-tick :ask-price [{price :price}]) 71 | (defmethod tick-handler :price-tick :ask-size [{price :price}]) 72 | 73 | 74 | (def last-ticker-id (atom 0)) 75 | (defn watch 76 | "Watch a contract and keep a scoreboard updates with the latest ticks" 77 | [contract] 78 | (let [connection (connect tick-handler) 79 | ticker-id (swap! last-ticker-id inc)] 80 | (request-market-data connection ticker-id contract ticker-id))) 81 | 82 | (defn print-pivots [pivots] 83 | (println "H/L/C: " (:high pivots) "/" (:low pivots) "/" (:close pivots)) 84 | (println "r3: " (:r3 pivots)) 85 | (println "r2: " (:r2 pivots)) 86 | (println "r1: " (:r1 pivots)) 87 | (println "pp: " (:pp pivots)) 88 | (println "s1: " (:s1 pivots)) 89 | (println "s2: " (:s2 pivots)) 90 | (println "s3: " (:s3 pivots))) 91 | 92 | (defn get-opening-trade [date] 93 | (let [cash-contract (index "INDU" "NYSE") 94 | future (futures-contract "YM" "ECBOT" (date-time 2011 12)) 95 | pivots (compute-daily-pivots cash-contract date)] 96 | (watch ))) 97 | 98 | -------------------------------------------------------------------------------- /examples/tick-recorder.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.examples.tick-recorder 2 | (:use [ib-re-actor.gateway] 3 | [clj-time.core :only [date-time minus now]] 4 | [clj-time.coerce :only [to-long]] 5 | [clojure.java.io] 6 | [clojure.tools.logging :only [debug warn error]] 7 | [clj-logging-config.log4j :only [set-logger!]])) 8 | 9 | (def contracts 10 | [ 11 | {:type :future :local-symbol "YM DEC 12" :exchange "ECBOT"} 12 | ;; {:type :future :local-symbol "YM MAR 13" :exchange "ECBOT"} 13 | ;; {:type :index :symbol "YM" :exchange "ECBOT"} 14 | ;; {:type :future :local-symbol "TFZ2" :exchange "NYBOT"} 15 | ;; {:type :future :local-symbol "TFH3" :exchange "NYBOT"} 16 | ;; {:type :index :symbol "TF" :exchange "NYBOT"} 17 | ;; {:type :future :local-symbol "ESZ2" :exchange "GLOBEX"} 18 | ;; {:type :future :local-symbol "ESH3" :exchange "GLOBEX"} 19 | ;; {:type :index :symbol "ES" :exchange "GLOBEX"} 20 | ;; {:type :future :local-symbol "NQZ2" :exchange "GLOBEX"} 21 | ;; {:type :future :local-symbol "NQH3" :exchange "GLOBEX"} 22 | ;; {:type :index :symbol "NQ" :exchange "GLOBEX"} 23 | ;; {:type :future :local-symbol "E7Z2" :exchange "GLOBEX"} 24 | ;; {:type :future :local-symbol "ZQ DEC 12" :exchange "ECBOT"} ; 30-day fed funds 25 | ;; {:type :future :local-symbol "ZN MAR 13" :exchange "ECBOT"} ; 10y treasury 26 | ;; {:type :index :symbol "TICK-NYSE" :exchange "NYSE"} 27 | ;; {:type :index :symbol "TRIN-NYSE" :exchange "NYSE"} 28 | ;; {:type :equity :symbol "XOM" :exchange "NYSE"} 29 | ;; {:type :equity :symbol "AAPL" :exchange "SMART" :currency "USD"} 30 | ;; {:type :equity :symbol "IBM" :exchange "NYSE" :currency "USD"} 31 | ;; {:type :equity :symbol "MSFT" :exchange "SMART" :currency "USD"} 32 | ;; {:type :equity :symbol "CVX" :exchange "NYSE" :currency "USD"} 33 | ;; {:type :equity :symbol "GE" :exchange "NYSE" :currency "USD"} 34 | ;; {:type :equity :symbol "T" :exchange "NYSE" :currency "USD"} 35 | ;; {:type :equity :symbol "PG" :exchange "NYSE" :currency "USD"} 36 | ;; {:type :equity :symbol "JNJ" :exchange "NYSE" :currency "USD"} 37 | ;; {:type :equity :symbol "PFE" :exchange "NYSE" :currency "USD"} 38 | ;; {:type :equity :symbol "SPY" :exchange "ARCA" :currency "USD"} 39 | ;; {:type :equity :symbol "SPYV" :exchange "ARCA" :currency "USD"} 40 | ;; {:type :equity :symbol "SPYG" :exchange "ARCA" :currency "USD"} 41 | ;; {:type :equity :symbol "IWM" :exchange "ARCA" :currency "USD"} 42 | ;; {:type :equity :symbol "IWN" :exchange "ARCA" :currency "USD"} 43 | ;; {:type :equity :symbol "IWO" :exchange "ARCA" :currency "USD"} 44 | ;; {:type :index :symbol "TICK-NASD" :exchange "NASDAQ"} 45 | ;; {:type :index :symbol "TRIN-NASD" :exchange "NASDAQ"} 46 | ]) 47 | 48 | (def ^:dynamic *done* (promise)) 49 | (def ^:dynamic *out-writer* nil) 50 | 51 | (defmulti message-handler :type) 52 | 53 | (defmethod message-handler :error 54 | [{:keys [request-id code message exception] :as msg}] 55 | (let [req (or request-id "???")] 56 | (cond 57 | (= 322 code) ; duplicate ticker id 58 | (debug "ignoring duplicate ticker id: " message) 59 | 60 | (contains? msg :message) 61 | (do 62 | (error "*** [" req "] " message) 63 | (when (not (warning? msg)) 64 | (deliver *done* true))) 65 | 66 | (contains? msg :exception) 67 | (do 68 | (error "*** [" req "] " exception "\n") 69 | (when (not (warning? msg)) 70 | (deliver *done* true)))))) 71 | 72 | (defmethod message-handler :tick [{:keys [contract field value]}] 73 | (debug "Got: " field " " value) 74 | (.write *out-writer* 75 | (str (now) "," 76 | symbol "," 77 | (:name field) "," val "\n"))) 78 | 79 | (defmethod message-handler :default [msg] 80 | (debug "??? unhandled message: " (prn-str msg))) 81 | 82 | (defn record-ticks [] 83 | (subscribe message-handler) 84 | (try 85 | (binding [*out-writer* (writer (file "ticks.csv") :append false) 86 | *done* (promise)] 87 | (doseq [contract contracts] 88 | (debug "Requesting " contract) 89 | (request-market-data contract)) 90 | @*done*) 91 | (finally 92 | (unsubscribe message-handler) 93 | (when (not (nil? *out-writer*)) 94 | (.close *out-writer*))))) 95 | 96 | (comment 97 | (set-logger! :level :debug) 98 | (connect "localhost" 4001)) 99 | -------------------------------------------------------------------------------- /src/ib_re_actor/synchronous.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.synchronous 2 | "This namespace wraps asynchronous functions with the appropriate magic to 3 | make them synchronous. 4 | 5 | These are much easier to use in an interactive context (such as when using the 6 | REPL) but probably not what you would want to use in an application, as the 7 | asynchronous API is a much more natural fit for building programs that react 8 | to events in market data." 9 | (:require 10 | [clojure.tools.logging :as log] 11 | [ib-re-actor.gateway :as g])) 12 | 13 | 14 | (defn single-value-handlers 15 | "This returns a map of handlers suitable for calls that will provide a single 16 | response." 17 | [result] 18 | {:data #(deliver result %) 19 | :error #(do (log/error %) 20 | (deliver result %))}) 21 | 22 | 23 | (defn resetting-handlers 24 | "This returns a map of handlers suitable for calls in which you are interested 25 | in the last response." 26 | [result] 27 | (let [data (atom nil)] 28 | {:data #(reset! data %) 29 | :end #(deliver result @data) 30 | :error #(do (log/error %) 31 | (deliver result %))})) 32 | 33 | 34 | (defn conjing-handlers 35 | "This returns a map of handlers suitable for calls that return a collection of 36 | items and you want to return them all." 37 | [result] 38 | (let [data (atom nil)] 39 | {:data #(swap! data conj %) 40 | :end #(deliver result @data) 41 | :error #(deliver result %)})) 42 | 43 | 44 | (defn server-time 45 | "Returns the server time" 46 | [connection] 47 | (let [result (promise)] 48 | (g/request-current-time connection 49 | (single-value-handlers result)) 50 | @result)) 51 | 52 | 53 | (defn market-snapshot 54 | "Returns a snapshot of the market for the specified contract." 55 | [connection contract] 56 | (let [result (promise) 57 | data (atom nil) 58 | handlers {:data (fn [{:keys [field value]}] (swap! data assoc field value)) 59 | :error #(reset! data %) 60 | :end #(deliver result @data)}] 61 | (g/request-market-data connection contract nil true handlers) 62 | @result)) 63 | 64 | 65 | (defn implied-vol 66 | "Returns detailed information about an option contract based on its price 67 | including implied volatility, greeks, etc." 68 | [connection contract option-price underlying-price] 69 | (let [result (promise)] 70 | (g/calculate-implied-vol connection contract option-price underlying-price 71 | (single-value-handlers result)) 72 | @result)) 73 | 74 | 75 | (defn option-price 76 | "Returns detailed information about an option contract based on its volatility 77 | including implied volatility, greeks, etc." 78 | [connection contract option-price underlying-price] 79 | (let [result (promise)] 80 | (g/calculate-option-price connection contract option-price underlying-price 81 | (single-value-handlers result)) 82 | @result)) 83 | 84 | 85 | (defn execute-order 86 | "Executes an order, returning only when the order is filled or canceled." 87 | [connection contract order] 88 | (let [result (promise)] 89 | (g/place-and-monitor-order connection contract order 90 | (resetting-handlers result)) 91 | @result)) 92 | 93 | 94 | (defn open-orders 95 | "Returns open orders" 96 | [connection] 97 | (let [result (promise)] 98 | (g/request-open-orders connection 99 | (conjing-handlers result)) 100 | @result)) 101 | 102 | 103 | (defn positions 104 | "Return account positions" 105 | [connection] 106 | (let [result (promise)] 107 | (g/request-positions connection (conjing-handlers result)) 108 | @result)) 109 | 110 | 111 | (defn contract-details 112 | "Gets details for the specified contract. 113 | 114 | Will return a list of contract details matching the contract description. A 115 | non-ambiguous contract will yield a list of one item." 116 | [connection contract] 117 | (let [result (promise)] 118 | (g/request-contract-details connection contract 119 | (conjing-handlers result)) 120 | @result)) 121 | 122 | 123 | (defn historical-data 124 | "Gets historical price bars for a contract." 125 | ([connection contract end-time duration duration-unit bar-size bar-size-unit 126 | what-to-show use-regular-trading-hours?] 127 | (let [result (promise)] 128 | (g/request-historical-data connection contract end-time 129 | duration duration-unit bar-size bar-size-unit 130 | what-to-show use-regular-trading-hours? 131 | (conjing-handlers result)) 132 | @result)) 133 | ([connection contract end 134 | duration duration-unit bar-size bar-size-unit] 135 | (historical-data connection contract end duration duration-unit 136 | bar-size bar-size-unit :trades true))) 137 | -------------------------------------------------------------------------------- /test/ib_re_actor/test/translation.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.test.translation 2 | (:require 3 | [clj-time.coerce :as c] 4 | [clj-time.core :refer [date-time interval local-date year-month]] 5 | [ib-re-actor.translation :refer [translate]] 6 | [midje.sweet :refer [fact tabular throws]])) 7 | 8 | (fact "unknown string codes just translate into themselves" 9 | (fact "coming from IB" 10 | (translate :from-ib :security-type "some weird value") 11 | => "some weird value") 12 | (fact "going out to IB" 13 | (translate :to-ib :security-type "some weird value") 14 | => "some weird value")) 15 | 16 | (fact "unknown keyword codes throw" 17 | (translate :to-ib :security-type :I-misspelled-something) 18 | => (throws #"^Can't translate to IB")) 19 | 20 | (tabular 21 | (fact "it can translate to IB durations" 22 | (translate :to-ib :duration [?value ?unit]) => ?expected) 23 | ?value ?unit ?expected 24 | 1 :second "1 S" 25 | 5 :seconds "5 S" 26 | 1 :day "1 D" 27 | 5 :days "5 D" 28 | 1 :week "1 W" 29 | 5 :weeks "5 W" 30 | 1 :year "1 Y" 31 | 5 :years "5 Y") 32 | 33 | (tabular 34 | (fact "it can translate to IB security codes" 35 | (translate :to-ib :security-type ?value) => ?expected) 36 | ?value ?expected 37 | :equity "STK" 38 | :option "OPT" 39 | :future "FUT" 40 | :index "IND" 41 | :future-option "FOP" 42 | :cash "CASH" 43 | :bag "BAG") 44 | 45 | (tabular 46 | (fact "it can translate from IB right" 47 | (translate :from-ib :right ?value) => ?expected) 48 | ?value ?expected 49 | "PUT" :put 50 | "P" :put 51 | "CALL" :call 52 | "C" :call 53 | "0" :none 54 | "?" :unknown) 55 | 56 | (tabular 57 | (fact "it can translate to IB right" 58 | (translate :to-ib :right ?value) => ?expected) 59 | ?value ?expected 60 | :put "PUT" 61 | :call "CALL" 62 | :none "0" 63 | :unknown "?") 64 | 65 | (tabular 66 | (fact "it can translate bar sizes" 67 | (translate :to-ib :bar-size [?value ?unit]) => ?expected) 68 | ?value ?unit ?expected 69 | 1 :second "1 secs" 70 | 5 :seconds "5 secs" 71 | 1 :minute "1 min" 72 | 3 :minutes "3 mins" 73 | 1 :hour "1 hour" 74 | 4 :hours "4 hours" 75 | 1 :day "1 day" 76 | 2 :days "2 days" 77 | 1 :week "1 W" 78 | 1 :month "1 M") 79 | 80 | (tabular 81 | (fact "it can translate what to show strings" 82 | (translate :to-ib :what-to-show ?value) => ?expected) 83 | ?value ?expected 84 | :trades "TRADES" 85 | :midpoint "MIDPOINT" 86 | :bid "BID" 87 | :ask "ASK" 88 | :bid-ask "BID_ASK" 89 | :historical-volatility "HISTORICAL_VOLATILITY" 90 | :option-implied-volatility "OPTION_IMPLIED_VOLATILITY" 91 | :option-volume "OPTION_VOLUME" 92 | :option-open-interest "OPTION_OPEN_INTEREST") 93 | 94 | (fact "it can translate from IB date-time values" 95 | (translate :from-ib :date-time (long 1000000000)) => (date-time 2001 9 9 1 46 40) 96 | (translate :from-ib :date-time "1000000000") => (date-time 2001 9 9 1 46 40)) 97 | 98 | (fact "it can translate date-times to IB expiry strings" 99 | (translate :to-ib :expiry (year-month 2011 9)) => "201109" 100 | (translate :to-ib :expiry (date-time 2012 10 20)) => "20121020") 101 | 102 | (fact "it can translate from IB expiry strings to joda time classes" 103 | (translate :from-ib :expiry "201509") => (year-month 2015 9) 104 | (translate :from-ib :expiry "20140203") => (local-date 2014 02 03)) 105 | 106 | (tabular 107 | (fact "it can translate time in force values" 108 | (translate :to-ib :time-in-force ?value) => ?expected) 109 | ?value ?expected 110 | :day "DAY" 111 | :good-to-close "GTC" 112 | :immediate-or-cancel "IOC" 113 | :good-till-date "GTD") 114 | 115 | (fact "it can translate date-times to the IB format" 116 | (translate :to-ib :date-time (date-time 2011)) => "20110101 00:00:00 UTC" 117 | (translate :to-ib :date-time (date-time 2001 4 1 13 30 29)) => "20010401 13:30:29 UTC") 118 | 119 | (tabular 120 | (fact "it can translate order actions" 121 | (translate :to-ib :order-action ?action) => ?expected) 122 | ?action ?expected 123 | :buy "BUY" 124 | :sell "SELL" 125 | :sell-short "SSHORT") 126 | 127 | (tabular 128 | (fact "it can translate to IB order types" 129 | (translate :to-ib :order-type ?type) => ?expected) 130 | ?type ?expected 131 | :limit "LMT") 132 | 133 | (tabular 134 | (fact "it can translate security id types" 135 | (translate :from-ib :security-id-type ?ib-type) => ?re-actor-type 136 | (translate :to-ib :security-id-type ?re-actor-type) => ?ib-type) 137 | ?re-actor-type ?ib-type 138 | :isin "ISIN" 139 | :cusip "CUSIP" 140 | :sedol "SEDOL" 141 | :ric "RIC") 142 | 143 | (tabular 144 | (fact "it can translate tick field codes" 145 | (translate :from-ib :tick-field-code ?ib-code) => ?re-actor-code 146 | (translate :to-ib :tick-field-code ?re-actor-code) => ?ib-code) 147 | ?re-actor-code ?ib-code 148 | :bid-size 0 149 | :bid-price 1 150 | :ask-price 2 151 | :ask-size 3) 152 | 153 | 154 | (tabular 155 | (fact "It can translate IB trading and liquid hours to joda intervals" 156 | (translate :from-ib :trading-hours [?tz ?ib-string]) => ?intervals) 157 | ?tz ?ib-string ?intervals 158 | "America/Belize" "20130115:1700-1515,1530-1615;20130116:1700-1515,1530-1615" 159 | [(interval (c/to-date-time "2013-01-14T23:00:00.000") (c/to-date-time "2013-01-15T21:15:00.000")) 160 | (interval (c/to-date-time "2013-01-15T21:30:00.000") (c/to-date-time "2013-01-15T22:15:00.000")) 161 | (interval (c/to-date-time "2013-01-15T23:00:00.000") (c/to-date-time "2013-01-16T21:15:00.000")) 162 | (interval (c/to-date-time "2013-01-16T21:30:00.000") (c/to-date-time "2013-01-16T22:15:00.000"))] 163 | 164 | "JST" "20130116:1630-2330,0900-1135,1145-1515;20130117:1630-2330,0900-1135,1145-1515" 165 | [(interval (c/to-date-time "2013-01-16T07:30:00.000") (c/to-date-time "2013-01-16T14:30:00.000")) 166 | (interval (c/to-date-time "2013-01-16T00:00:00.000") (c/to-date-time "2013-01-16T02:35:00.000")) 167 | (interval (c/to-date-time "2013-01-16T02:45:00.000") (c/to-date-time "2013-01-16T06:15:00.000")) 168 | (interval (c/to-date-time "2013-01-17T07:30:00.000") (c/to-date-time "2013-01-17T14:30:00.000")) 169 | (interval (c/to-date-time "2013-01-17T00:00:00.000") (c/to-date-time "2013-01-17T02:35:00.000")) 170 | (interval (c/to-date-time "2013-01-17T02:45:00.000") (c/to-date-time "2013-01-17T06:15:00.000"))] 171 | 172 | "EST" "20130115:1715-1700;20130116:1715-1700" 173 | [(interval (c/to-date-time "2013-01-14T22:15:00.000") (c/to-date-time "2013-01-15T22:00:00.000")) 174 | (interval (c/to-date-time "2013-01-15T22:15:00.000") (c/to-date-time "2013-01-16T22:00:00.000"))] 175 | 176 | "EST" "20130115:CLOSED;20130116:1715-1700" 177 | [(interval (c/to-date-time "2013-01-15T05:00:00.000") (c/to-date-time "2013-01-15T05:00:00.000")) 178 | (interval (c/to-date-time "2013-01-15T22:15:00.000") (c/to-date-time "2013-01-16T22:00:00.000"))]) 179 | -------------------------------------------------------------------------------- /test/ib_re_actor/test/mapping.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.test.mapping 2 | (:require 3 | [clj-time.core :refer [date-time year-month]] 4 | [ib-re-actor.mapping :refer [->map map->]] 5 | [midje.sweet :refer [fact]]) 6 | (:import 7 | (com.ib.client Contract ContractDetails Order OrderState Execution ExecutionFilter CommissionReport))) 8 | 9 | (defn invoke-private-ctor [type] 10 | (let [ctor (first (.getDeclaredConstructors type))] 11 | (.setAccessible ctor true) 12 | (.newInstance ctor nil))) 13 | 14 | (defmacro defmappingtest 15 | "Checking all these fields is pretty tedious, but important and defining the mappings 16 | is error-prone, so I think it's important that we test them. To relieve the tedium, I 17 | will just define a macro that generates the tests in the form I was using before. 18 | 19 | Specs are a vector of: 20 | <> 21 | <> 22 | <> 23 | <> 24 | 25 | You can have duplicate rows for the same field<->map key to try out different values" 26 | [type & specs] 27 | (let [obj (gensym "object") 28 | map-obj (gensym "map") 29 | [options specs] (split-with keyword? specs) 30 | options (set options) 31 | ctor (if (options :private-constructor) 32 | `(invoke-private-ctor ~type) 33 | `(new ~type))] 34 | `(do 35 | ;;; it's kind of handy to be able to look at an example sometimes 36 | (def ~(symbol (str "example-" type)) 37 | (let [~obj ~ctor] 38 | ~@(for [[k field mv ov] specs] 39 | `(set! (. ~obj ~field) ~(or ov mv))) 40 | ~obj)) 41 | 42 | (def ~(symbol (str "example-" type "-map")) 43 | ~(zipmap (map #(% 0) specs) 44 | (map #(% 2) specs))) 45 | 46 | (fact ~(str "mapping " type) 47 | (fact "object -> map" 48 | (let [~obj ~ctor] 49 | ~@(for [[k field mv ov] specs] 50 | `(set! (. ~obj ~field) ~(or ov mv))) 51 | (let [~map-obj (->map ~obj)] 52 | ~@(for [[k field mv _] specs] 53 | `(fact ~(str field " maps to " k) 54 | (~map-obj ~k) => ~mv))))) 55 | ~(when (not (options :private-constructor)) 56 | `(fact "map -> object" 57 | (let [~map-obj ~(zipmap (map #(% 0) specs) 58 | (map #(% 2) specs)) 59 | ~obj (map-> ~type ~map-obj)] 60 | ~@(for [[k field mv ov] specs] 61 | `(fact ~(str k " maps to " field) 62 | (. ~obj ~field) => ~(or ov mv)))))))))) 63 | 64 | (comment 65 | (clojure.pprint/pprint (macroexpand-1 '(defmappingtest Thingy [:id m_id 1 "foo"]))) 66 | (clojure.pprint/pprint (macroexpand-1 '(defmappingtest Thingy 67 | :private-constructor 68 | [:id m_id 1 "foo"]))) 69 | ) 70 | 71 | 72 | (defmappingtest Contract 73 | [:contract-id m_conId 1] 74 | [:symbol m_symbol "some symbol"] 75 | [:type m_secType :equity "STK"] 76 | [:expiry m_expiry (year-month 2000 1) "200001"] 77 | [:strike m_strike 18.0] 78 | [:put-call-right m_right :put "PUT"] 79 | [:multiplier m_multiplier 234.567 "234.567"] 80 | [:exchange m_exchange "some exchange"] 81 | [:currency m_currency "some currency"] 82 | [:local-symbol m_localSymbol "some local symbol"] 83 | [:trading-class m_tradingClass "some trading class"] 84 | [:primary-exchange m_primaryExch "some primary exchange"] 85 | [:include-expired? m_includeExpired true] 86 | [:security-id-type m_secIdType :isin "ISIN"] 87 | [:security-id m_secId "AB1234567890"] 88 | [:combo-legs-description m_comboLegsDescrip "some description of combo legs"] 89 | ;; TODO: Figure out what to do about m_comboLegs and m_underComp 90 | ) 91 | 92 | (defmappingtest ContractDetails 93 | ;; TODO: figure out what to do about m_summary 94 | [:market-name m_marketName "some market name"] 95 | [:min-tick m_minTick 2.] 96 | [:price-magnifier m_priceMagnifier 4] 97 | [:order-types m_orderTypes [:limit :market] "LMT,MKT"] 98 | [:valid-exchanges m_validExchanges ["GLOBEX" "ECBOT"] "GLOBEX,ECBOT"] 99 | [:underlying-contract-id m_underConId 9876] 100 | [:long-name m_longName "some long name"] 101 | [:cusip m_cusip "459200101"] 102 | [:ratings m_ratings "some credit ratings"] 103 | [:description-details m_descAppend "some more description stuff"] 104 | [:bond-type m_bondType "some bond type"] 105 | [:coupon-type m_couponType "some coupon type"] 106 | [:callable? m_callable true] 107 | [:putable? m_putable true] 108 | [:coupon m_coupon 1.23] 109 | [:convertible? m_convertible true] 110 | [:maturity m_maturity (date-time 2020 2 3) "02/03/2020"] 111 | [:issue-date m_issueDate (date-time 2010 4 5) "04/05/2010"] 112 | [:next-option-date m_nextOptionDate (date-time 2013 5 6) "05/06/2013"] 113 | [:next-option-type m_nextOptionType "some option type"] 114 | [:next-option-partial m_nextOptionPartial true] 115 | [:notes m_notes "some notes"] 116 | [:contract-month m_contractMonth "some contract month"] 117 | [:industry m_industry "some industry"] 118 | [:category m_category "some category"] 119 | [:subcategory m_subcategory "some sub-category"] 120 | [:time-zone-id m_timeZoneId "some time zone id"] 121 | [:trading-hours m_tradingHours "some trading hours"] 122 | [:liquid-hours m_liquidHours "some liquid hours"]) 123 | 124 | (defmappingtest Execution 125 | [:account-code m_acctNumber "some account number"] 126 | [:average-price m_avgPrice 23.45] 127 | [:client-id m_clientId 1] 128 | [:cummulative-quantity m_cumQty 2] 129 | [:exchange m_exchange "some exchange"] 130 | [:execution-id m_execId "some execution id"] 131 | [:liquidate-last m_liquidation 10] 132 | [:order-id m_orderId 4] 133 | [:permanent-id m_permId 5] 134 | [:price m_price 6.78] 135 | [:shares m_shares 9] 136 | [:side m_side :buy "BOT"] 137 | [:time m_time "some time"]) 138 | 139 | (defmappingtest ExecutionFilter 140 | [:client-id m_clientId 1] 141 | [:account-code m_acctCode "some account code"] 142 | 143 | ;; TODO: this should be a date-time 144 | [:after-time m_time "20000102-23:59:59 UTC"] 145 | [:order-symbol m_symbol "some symbol"] 146 | [:security-type m_secType :equity "STK"] 147 | [:exchange m_exchange "GLOBEX"] 148 | [:side m_side :buy "BUY"]) 149 | 150 | (defmappingtest Order 151 | [:account-code m_account "some account code"] 152 | [:order-id m_orderId 1] 153 | [:client-id m_clientId 2] 154 | [:permanent-id m_permId 3] 155 | [:transmit? m_transmit false] 156 | [:quantity m_totalQuantity 4] 157 | [:action m_action :buy "BUY"] 158 | [:type m_orderType :limit "LMT"] 159 | [:block-order? m_blockOrder true] 160 | [:sweep-to-fill? m_sweepToFill true] 161 | [:time-in-force m_tif :day "DAY"] 162 | [:good-after-time m_goodAfterTime "20121213 23:59:59"] 163 | [:good-till-date m_goodTillDate "20121214 23:59:59"] 164 | [:outside-regular-trading-hours? m_outsideRth true] 165 | [:hidden? m_hidden true] 166 | [:all-or-none? m_allOrNone true] 167 | [:limit-price m_lmtPrice 23.99] 168 | [:discretionary-amount m_discretionaryAmt 1.99] 169 | [:stop-price m_auxPrice 24.99]) 170 | 171 | (defmappingtest OrderState :private-constructor 172 | [:status m_status :filled "Filled"] 173 | [:initial-margin m_initMargin "23.45"] 174 | [:maintenance-margin m_maintMargin "34.56"] 175 | [:equity-with-loan m_equityWithLoan "45.67"] 176 | [:commission m_commission 56.78] 177 | [:minimum-commission m_minCommission 67.89] 178 | [:maximum-commission m_maxCommission 78.90] 179 | [:commission-currency m_commissionCurrency "ABC"] 180 | [:warning-text m_warningText "some warning text"]) 181 | 182 | (defmappingtest CommissionReport :private-constructor 183 | [:commission m_commission 23.45] 184 | [:currency m_currency "some currency code"] 185 | [:execution-id m_execId "some execution id"] 186 | [:realized-profit-loss m_realizedPNL 34.56] 187 | [:yield m_yield 4.56] 188 | [:yield-redemption-date m_yieldRedemptionDate 20101031]) 189 | -------------------------------------------------------------------------------- /src/ib_re_actor/mapping.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.mapping 2 | "Functions for mapping to and from Interactive Brokers classes. It is much easier to work 3 | with maps in clojure, so we use these functions internally on all the data we exchange 4 | with the Interactive Brokers API. 5 | 6 | In addition to just converting to maps, we also use these functions to translate some 7 | primitives: strings with constant values into keywords, booleans in strings into booleans, 8 | date strings into clj-time dates, etc." 9 | (:require 10 | [clojure.string :refer [join]] 11 | [ib-re-actor.translation :refer [translate]]) 12 | (:import 13 | (com.ib.client Contract))) 14 | 15 | (defprotocol Mappable 16 | (->map [this] 17 | "Create a map with the all the non-the non-null properties of object.")) 18 | 19 | (defmulti map-> (fn [type _] type)) 20 | 21 | (defn- assoc-if-val-non-nil 22 | "Chainable, conditional assoc. If v is not nil, assoc it and return the result, 23 | otherwise, don't and return m unchanged." 24 | ([m k v] 25 | (if (nil? v) m (assoc m k v))) 26 | ([m k v translation] 27 | (if (nil? v) m (assoc m k (translate :from-ib translation v))))) 28 | 29 | (defn- assoc-nested [m k v] 30 | (if (nil? v) m (assoc m k (->map v)))) 31 | 32 | (defn emit-map<-field 33 | "When mapping from an object to a clojure map, this creates a call to assoc in the value. 34 | optional parameters: 35 | 36 | :translation <>: 37 | Specifying this option will add a call to (translate to-from ...) in each field 38 | setter or assoc when mapping to and from objects. 39 | 40 | :nested <>: 41 | Specifying this will map a nested instance of another class." 42 | [this [k field & options]] 43 | (let [{:keys [translation nested]} (apply hash-map options) 44 | m (gensym "m")] 45 | (cond 46 | (not (nil? translation)) `((assoc-if-val-non-nil ~k (. ~this ~field) ~translation)) 47 | (not (nil? nested)) `((assoc-nested ~k (. ~this ~field))) 48 | :else `((assoc-if-val-non-nil ~k (. ~this ~field)))))) 49 | 50 | (defn emit-map->field 51 | "When mapping from a clojure map to an object, this creates a call to set the associated 52 | field on the object." 53 | [m this [key field & options]] 54 | (let [{:keys [translation nested]} (apply hash-map options) 55 | val (gensym "val")] 56 | `((if (contains? ~m ~key) 57 | (let [~val (~key ~m)] 58 | (try 59 | (set! (. ~this ~field) 60 | ~(cond 61 | (not (nil? translation)) `(translate :to-ib ~translation ~val) 62 | (not (nil? nested)) `(map-> ~nested ~val) 63 | :else `~val)) 64 | (catch ClassCastException ex# 65 | (throw (ex-info (str "Failed to map field " ~(str field) 66 | ~(when translation 67 | (str ", using translation " translation)) 68 | ", value \"" ~val "\"") 69 | {:class (class ~this) 70 | :key ~key 71 | :field ~(str field) 72 | :translation ~translation} 73 | ex#))))))))) 74 | 75 | (defmacro defmapping 76 | "This is used to extend an Interactive Brokers API class with a method to convert it into 77 | a clojure map, and using the same information, add a method to the map-> multimethod to 78 | convert maps into instances of the IB class." 79 | [c & field-keys] 80 | (let [this (gensym "this") 81 | field-map (gensym "field-map") 82 | valid-keys (gensym "valid-keys")] 83 | `(do 84 | (extend-type ~c 85 | Mappable 86 | (->map [~this] 87 | (-> {} ~@(mapcat (partial emit-map<-field this) field-keys)))) 88 | 89 | (defmethod map-> ~c [_# ~field-map] 90 | (let [~this (new ~c)] 91 | ~@(mapcat (partial emit-map->field field-map this) field-keys) 92 | ~this))))) 93 | 94 | (defmacro defmapping-readonly 95 | "Same as defmapping, but for classes that don't have public constructors. Since we can't 96 | create instances, we will only map from objects to clojure maps." 97 | [c & field-keys] 98 | (let [this (gensym "this")] 99 | `(extend-type ~c 100 | Mappable 101 | (->map [~this] 102 | (-> {} ~@(mapcat (partial emit-map<-field this) field-keys)))))) 103 | 104 | (defmapping com.ib.client.Contract 105 | [:contract-id m_conId] 106 | [:symbol m_symbol] 107 | [:type m_secType :translation :security-type] 108 | [:expiry m_expiry :translation :expiry] 109 | [:strike m_strike] 110 | [:put-call-right m_right :translation :right] 111 | [:multiplier m_multiplier :translation :double-string] 112 | [:exchange m_exchange] 113 | [:currency m_currency] 114 | [:local-symbol m_localSymbol] 115 | [:trading-class m_tradingClass] 116 | [:primary-exchange m_primaryExch] 117 | [:include-expired? m_includeExpired] 118 | [:security-id-type m_secIdType :translation :security-id-type] 119 | [:security-id m_secId] 120 | [:combo-legs-description m_comboLegsDescrip] 121 | [:combo-legs m_comboLegs] 122 | [:underlying-component m_underComp]) 123 | 124 | (defmapping com.ib.client.ContractDetails 125 | [:summary m_summary :nested com.ib.client.Contract] 126 | [:market-name m_marketName] 127 | [:min-tick m_minTick] 128 | [:price-magnifier m_priceMagnifier] 129 | [:order-types m_orderTypes :translation :order-types] 130 | [:valid-exchanges m_validExchanges :translation :exchanges] 131 | [:underlying-contract-id m_underConId] 132 | [:long-name m_longName] 133 | [:contract-month m_contractMonth] 134 | [:industry m_industry] 135 | [:category m_category] 136 | [:subcategory m_subcategory] 137 | [:time-zone-id m_timeZoneId] 138 | [:trading-hours m_tradingHours] 139 | [:liquid-hours m_liquidHours] 140 | [:ev-rule m_evRule] 141 | [:ev-multiplier m_evMultiplier] 142 | [:cusip m_cusip] 143 | [:ratings m_ratings] 144 | [:description-details m_descAppend] 145 | [:bond-type m_bondType] 146 | [:coupon-type m_couponType] 147 | [:callable? m_callable] 148 | [:putable? m_putable] 149 | [:coupon m_coupon] 150 | [:convertible? m_convertible] 151 | [:maturity m_maturity :translation :date] 152 | [:issue-date m_issueDate :translation :date] 153 | [:next-option-date m_nextOptionDate :translation :date] 154 | [:next-option-type m_nextOptionType] 155 | [:next-option-partial m_nextOptionPartial] 156 | [:notes m_notes]) 157 | 158 | (defmapping com.ib.client.ExecutionFilter 159 | [:client-id m_clientId] 160 | [:account-code m_acctCode] 161 | [:after-time m_time :translation :timestamp] 162 | [:order-symbol m_symbol] 163 | [:security-type m_secType :translation :security-type] 164 | [:exchange m_exchange] 165 | [:side m_side :translation :order-action]) 166 | 167 | (defmapping com.ib.client.Execution 168 | [:account-code m_acctNumber] 169 | [:average-price m_avgPrice] 170 | [:client-id m_clientId] 171 | [:cummulative-quantity m_cumQty] 172 | [:exchange m_exchange] 173 | [:execution-id m_execId] 174 | [:liquidate-last m_liquidation] 175 | [:order-id m_orderId] 176 | [:permanent-id m_permId] 177 | [:price m_price] 178 | [:shares m_shares] 179 | [:side m_side :translation :execution-side] 180 | [:time m_time]) 181 | 182 | (defmapping com.ib.client.Order 183 | [:account-code m_account] 184 | [:order-id m_orderId] 185 | [:client-id m_clientId] 186 | [:permanent-id m_permId] 187 | [:transmit? m_transmit] 188 | [:quantity m_totalQuantity] 189 | [:action m_action :translation :order-action] 190 | [:type m_orderType :translation :order-type] 191 | [:block-order? m_blockOrder] 192 | [:sweep-to-fill? m_sweepToFill] 193 | [:time-in-force m_tif :translation :time-in-force] 194 | [:good-after-time m_goodAfterTime] 195 | [:good-till-date m_goodTillDate] 196 | [:outside-regular-trading-hours? m_outsideRth] 197 | [:hidden? m_hidden] 198 | [:all-or-none? m_allOrNone] 199 | [:limit-price m_lmtPrice] 200 | [:discretionary-amount m_discretionaryAmt] 201 | [:stop-price m_auxPrice]) 202 | 203 | (defmapping-readonly com.ib.client.OrderState 204 | [:status m_status :translation :order-status] 205 | [:initial-margin m_initMargin] 206 | [:maintenance-margin m_maintMargin] 207 | [:equity-with-loan m_equityWithLoan] 208 | [:commission m_commission] 209 | [:minimum-commission m_minCommission] 210 | [:maximum-commission m_maxCommission] 211 | [:commission-currency m_commissionCurrency] 212 | [:warning-text m_warningText]) 213 | 214 | (defmapping-readonly com.ib.client.CommissionReport 215 | [:commission m_commission] 216 | [:currency m_currency] 217 | [:execution-id m_execId] 218 | [:realized-profit-loss m_realizedPNL] 219 | [:yield m_yield] 220 | [:yield-redemption-date m_yieldRedemptionDate :translate :yield-redemption-date]) 221 | -------------------------------------------------------------------------------- /test/ib_re_actor/test/wrapper.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.test.wrapper 2 | (:require 3 | [clj-time.core :refer [date-time]] 4 | [ib-re-actor.mapping :refer [->map]] 5 | [ib-re-actor.wrapper :refer [create]] 6 | [midje.sweet :refer [fact]] 7 | [midje.util :refer [testable-privates]]) 8 | (:import 9 | (com.ib.client Contract Order OrderState ContractDetails Execution))) 10 | 11 | 12 | (testable-privates ib-re-actor.wrapper dispatch-message) 13 | 14 | (def some-contract {:symbol "SOME TICKER"}) 15 | (def some-contract-id 42) 16 | 17 | (defn make-order-state [] 18 | (let [ctor (first (.getDeclaredConstructors OrderState))] 19 | (.setAccessible ctor true) 20 | (.newInstance ctor nil))) 21 | 22 | 23 | (defmacro wrapper->message 24 | "Given 1 or more wrapper calls, creates a wrapper and applies the method calls to the 25 | wrapper, collecting and returning any messages the wrapper dispatched." 26 | [& calls] 27 | (let [wrapper (gensym "wrapper")] 28 | `(let [messages# (atom nil) 29 | ~wrapper (create nil)] 30 | (with-redefs [ib-re-actor.wrapper/dispatch-message 31 | (fn [_# m#] (swap! messages# conj m#))] 32 | ~@(map #(concat [`. wrapper] %) calls)) 33 | (first @messages#)))) 34 | 35 | 36 | (fact "when IB sends the current time, it dispatches a current time message" 37 | (wrapper->message (currentTime 1000000000)) 38 | => {:type :current-time :value (date-time 2001 9 9 1 46 40)}) 39 | 40 | (fact "historicalData messages from IB" 41 | (wrapper->message (historicalData 1 "1000000000" 2.0 3.0 4.0 5.0 6 7 8.0 true)) 42 | => {:type :price-bar :request-id 1 43 | :value {:time (date-time 2001 9 9 1 46 40) :open 2.0 :close 5.0 44 | :high 3.0 :low 4.0 :volume 6 :trade-count 7 :WAP 8.0 45 | :has-gaps? true}}) 46 | 47 | (fact "historicalData complete messages from IB" 48 | (wrapper->message 49 | (historicalData 1 "finished" 0.0 0.0 0.0 0.0 0 0 0.0 false)) 50 | => {:type :price-bar-complete :request-id 1}) 51 | 52 | (fact "realtime bars" 53 | (wrapper->message (realtimeBar 1 1000000000 54 | 1.0 2.0 3.0 4.0 5 6.0 7)) 55 | => {:type :price-bar :request-id 1 56 | :value {:time (date-time 2001 9 9 1 46 40) 57 | :open 1.0 :high 2.0 :low 3.0 :close 4.0 :volume 5 :count 7 58 | :WAP 6.0}}) 59 | 60 | (fact "price ticks" 61 | (wrapper->message (tickPrice some-contract-id 2 3.0 1)) 62 | => {:type :tick :ticker-id some-contract-id 63 | :value {:field :ask-price :value 3.0 64 | :can-auto-execute? true}}) 65 | 66 | (fact "size ticks" 67 | (wrapper->message (tickSize some-contract-id 3 4)) 68 | => {:type :tick :ticker-id some-contract-id 69 | :value {:field :ask-size :value 4}}) 70 | 71 | (fact "option computation ticks" 72 | (wrapper->message 73 | (tickOptionComputation some-contract-id 10 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0)) 74 | => {:type :tick :ticker-id some-contract-id 75 | :value {:field :bid-option-computation 76 | :value {:implied-volatility 2.0 :delta 3.0 77 | :option-price 4.0 :pv-dividends 5.0 :gamma 6.0 :vega 7.0 78 | :theta 8.0 :underlying-price 9.0}}}) 79 | 80 | (fact "generic ticks" 81 | (wrapper->message (tickGeneric some-contract-id 50 2.0)) 82 | => {:type :tick :ticker-id some-contract-id 83 | :value {:field :bid-yield 84 | :value 2.0}}) 85 | 86 | (fact "string ticks" 87 | (fact "last timestamp ticks" 88 | (wrapper->message 89 | (tickString some-contract-id 45 "1000000000")) 90 | => {:type :tick :ticker-id some-contract-id 91 | :value {:field :last-timestamp 92 | :value (date-time 2001 9 9 1 46 40)}})) 93 | 94 | (fact "EFP ticks" 95 | (wrapper->message (tickEFP some-contract-id 38 2.0 "0.03 %" 4.0 5 96 | "2001-04-01" 6.0 7.0)) 97 | => {:type :tick :ticker-id some-contract-id 98 | :value {:field :bid-efp-computation 99 | :basis-points 2.0 100 | :formatted-basis-points "0.03 %" 101 | :implied-future 4.0 :hold-days 5 :future-expiry "2001-04-01" 102 | :dividend-impact 6.0 :dividends-to-expiry 7.0}}) 103 | 104 | (fact "snapshot end" 105 | (wrapper->message (tickSnapshotEnd 1)) 106 | => {:type :tick-snapshot-end :request-id 1}) 107 | 108 | (fact "connection closed" 109 | (wrapper->message (connectionClosed)) 110 | => {:type :connection-closed}) 111 | 112 | (fact "errors" 113 | (fact "specific to a particular request" 114 | (wrapper->message (error 1 99999 "some message")) 115 | => {:type :error :id 1 :code 99999 :message "some message"}) 116 | (fact "just a message" 117 | (wrapper->message (error "some message")) 118 | => {:type :error :message "some message"}) 119 | (fact "exceptions" 120 | (let [ex (Exception. "some problem")] 121 | (wrapper->message (error ex)) 122 | => {:type :error :exception "java.lang.Exception: some problem"}))) 123 | 124 | (fact "time messages" 125 | (wrapper->message (currentTime 1000000000)) 126 | => {:type :current-time :value (date-time 2001 9 9 1 46 40)}) 127 | 128 | (fact "order status updates" 129 | (wrapper->message (orderStatus 1 "PendingSubmit" 2 3 4.0 5 6 7.0 8 "locate")) 130 | => {:type :order-status :order-id 1 131 | :value {:status :pending-submit 132 | :filled 2 :remaining 3 :average-fill-price 4.0 :permanent-id 5 133 | :parent-id 6 :last-fill-price 7.0 :client-id 8 134 | :why-held "locate"}}) 135 | 136 | (fact "open order updates" 137 | (let [order (Order.) 138 | mapped-order (->map order) 139 | order-state (make-order-state) 140 | mapped-order-state (->map order-state) 141 | contract (Contract.) 142 | mapped-contract (->map contract)] 143 | (wrapper->message (openOrder 1 contract order order-state)) 144 | => {:type :open-order :order-id 1 145 | :value {:contract mapped-contract 146 | :order mapped-order :order-state mapped-order-state}})) 147 | 148 | (fact "order end messages" 149 | (wrapper->message (openOrderEnd)) 150 | => {:type :open-order-end}) 151 | 152 | (fact "next valid id" 153 | (wrapper->message (nextValidId 42)) 154 | => {:type :next-valid-order-id :value 42}) 155 | 156 | (fact "updating account value" 157 | (fact "integer account value" 158 | (wrapper->message 159 | (updateAccountValue "DayTradesRemaining" "5" nil "some account")) 160 | => {:type :update-account-value 161 | :value {:key :day-trades-remaining 162 | :value 5 :currency nil :account "some account"}}) 163 | (fact "numeric account value" 164 | (wrapper->message 165 | (updateAccountValue "CashBalance" "123.456" "ZWD" "some account")) 166 | => {:type :update-account-value 167 | :value {:key :cash-balance 168 | :value 123.456 :currency "ZWD" :account "some account"}}) 169 | (fact "boolean account value" 170 | (fact "true value" 171 | (wrapper->message 172 | (updateAccountValue "AccountReady" "true" nil "some account")) 173 | => {:type :update-account-value 174 | :value {:key :account-ready 175 | :value true :currency nil :account "some account"}}) 176 | (fact "false value" 177 | (wrapper->message 178 | (updateAccountValue "AccountReady" "false" nil "some account")) 179 | => {:type :update-account-value 180 | :value {:key :account-ready 181 | :value false :currency nil :account "some account"}})) 182 | (fact "other type of account value" 183 | (wrapper->message 184 | (updateAccountValue "AccountCode" "some code" nil "some account")) 185 | => {:type :update-account-value 186 | :value {:key :account-code 187 | :value "some code" :currency nil :account "some account"}})) 188 | 189 | (fact "updates to portfolio" 190 | (let [contract (Contract.) 191 | mapped-contract (->map contract)] 192 | (wrapper->message (updatePortfolio contract 1 2.0 3.0 4.0 5.0 6.0 "some account")) 193 | => {:type :update-portfolio 194 | :value {:contract mapped-contract :position 1 195 | :market-price 2.0 :market-value 3.0 :average-cost 4.0 196 | :unrealized-gain-loss 5.0 :realized-gain-loss 6.0 197 | :account "some account"}})) 198 | 199 | (fact "last update date of the account information" 200 | (wrapper->message (updateAccountTime "13:45")) 201 | => {:type :update-account-time :value (date-time 1970 1 1 13 45)}) 202 | 203 | (fact "contract details" 204 | (let [cd (ContractDetails.) 205 | mapped-cd (->map cd)] 206 | (wrapper->message (contractDetails 1 cd)) 207 | => {:type :contract-details :request-id 1 :value mapped-cd})) 208 | 209 | (fact "when contract details are done" 210 | (wrapper->message (contractDetailsEnd 42)) 211 | => {:type :contract-details-end :request-id 42}) 212 | 213 | (fact "it can give me bond contract details" 214 | (let [cd (ContractDetails.) 215 | mapped-cd (->map cd)] 216 | (wrapper->message (bondContractDetails 1 cd)) 217 | => {:type :contract-details :request-id 1 :value mapped-cd})) 218 | 219 | (fact "execution details" 220 | (let [contract (Contract.) 221 | mapped-contract (->map contract) 222 | execution (Execution.) 223 | mapped-execution (->map execution)] 224 | (wrapper->message (execDetails 1 contract execution)) 225 | => {:type :execution-details :request-id 1 226 | :value {:contract mapped-contract :value mapped-execution}})) 227 | 228 | (fact "when execution details are done" 229 | (wrapper->message (execDetailsEnd 1)) 230 | => {:type :execution-details-end :request-id 1}) 231 | 232 | (fact "when market depth changes" 233 | (wrapper->message (updateMktDepth some-contract-id 2 0 1 3.0 4)) 234 | => {:type :update-market-depth :ticker-id some-contract-id 235 | :value {:position 2 236 | :operation :insert :side :bid :price 3.0 :size 4}}) 237 | 238 | (fact "when the Level II market depth changes" 239 | (wrapper->message (updateMktDepthL2 some-contract-id 2 "some market maker" 240 | 1 0 3.0 4)) 241 | => {:type :update-market-depth-level-2 :ticker-id some-contract-id 242 | :value {:position 2 243 | :market-maker "some market maker" :operation :update :side :ask 244 | :price 3.0 :size 4}}) 245 | 246 | (fact "when there is a new news bulletin" 247 | (fact "in general" 248 | (wrapper->message (updateNewsBulletin 1 0 "some message text" "some exchange")) 249 | => {:type :news-bulletin :id 1 250 | :value {:type :news-bulletin 251 | :message "some message text" 252 | :exchange "some exchange"}}) 253 | (fact "saying an exchange in unavailable" 254 | (wrapper->message (updateNewsBulletin 2 1 "typhoon shuts down HK Exchange!!!" 255 | "HKSE")) 256 | => {:type :news-bulletin :id 2 257 | :value {:type :exchange-unavailable 258 | :message "typhoon shuts down HK Exchange!!!" 259 | :exchange "HKSE"}}) 260 | (fact "saying an exchange is available again" 261 | (wrapper->message (updateNewsBulletin 3 2 "HK Exchange back in business" 262 | "HKSE")) 263 | => {:type :news-bulletin :id 3 264 | :value {:type :exchange-available 265 | :message "HK Exchange back in business" 266 | :exchange "HKSE"}})) 267 | 268 | (fact "getting a list of managed accounts" 269 | (wrapper->message (managedAccounts "account1, account2, account3")) 270 | => {:type :managed-accounts :value ["account1", "account2", "account3"]}) 271 | 272 | (fact "getting Financial Advisor information" 273 | (fact "groups" 274 | (wrapper->message (receiveFA 1 "")) 275 | => {:type :financial-advisor-groups :value ""}) 276 | (fact "profile" 277 | (wrapper->message (receiveFA 2 "")) 278 | => {:type :financial-advisor-profile :value ""}) 279 | (fact "account aliases" 280 | (wrapper->message (receiveFA 3 "")) 281 | => {:type :financial-advisor-account-aliases 282 | :value ""})) 283 | 284 | (fact "getting valid scanner parameters" 285 | (wrapper->message 286 | (scannerParameters "")) 287 | => {:type :scan-parameters 288 | :value ""}) 289 | 290 | (fact "getting scanner results" 291 | (let [cd (ContractDetails.) 292 | mapped-cd (->map cd)] 293 | (wrapper->message (scannerData 1 2 cd "some distance" "some benchmark" 294 | "some projection" "some efp combo legs")) 295 | => {:type :scan-result :request-id 1 296 | :value {:rank 2 :contract-details mapped-cd 297 | :distance "some distance" :benchmark "some benchmark" 298 | :projection "some projection" :legs "some efp combo legs"}})) 299 | 300 | (fact "when a scan is done" 301 | (wrapper->message (scannerDataEnd 1)) 302 | => {:type :scan-end :request-id 1}) 303 | -------------------------------------------------------------------------------- /src/ib_re_actor/gateway.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.gateway 2 | "The main user interface. Functions for connecting to Interactive Brokers TWS 3 | Gateway and sending requests to it. 4 | 5 | The IB API requires consumers to instantate an object of type 6 | com.ib.client.EClientSocket which is a socket client to the gateway, or TWS. 7 | When instantating this object you pass in another object which must implement 8 | the com.ib.client.EWrapper interface. The EWrapper is a collection of 9 | callbacks. You then invoke methods (like requesting price data) on the the 10 | EClientSocket object and the results (price update events) is returned by the 11 | EWrapper callbacks. So the typical pattern for dealing with the IB API is to 12 | implement an EWrapper type object for each application, which requires a lot 13 | of knowledge of the API. 14 | 15 | This library takes a slightly different approach to try ease that burden. The 16 | library implements a listener framework around each callback in the EWrapper 17 | object. So what happens is that each time the IB Gateway calls back to an 18 | method in the EWrapper, our object parses the response and packages it up into 19 | a tidy Clojure map which its hands to any registered listeners. 20 | 21 | Consumers of this library thus do not need to care about the mechanics of 22 | EWrapper, ESocketClient etc, they simply need to register a listener and will 23 | receive events. 24 | 25 | Basic Usage: 26 | 27 | 1. Connect to a running Gateway of TWS, using the connect function 28 | 2. Register a listener for an event using the subscribe function 29 | 3. Request the generation of the appropriate event using the request-* functions 30 | 4. Cancel the request with the cancel-* when done." 31 | (:require 32 | [clojure.tools.logging :as log] 33 | [ib-re-actor.client-socket :as cs] 34 | [ib-re-actor.wrapper :as wrapper :refer [error-end? matching-message? request-end?]])) 35 | 36 | ;; Default port for live trading 37 | (defonce default-port 7496) 38 | ;; Default port for paper trading (not real money) 39 | (defonce default-paper-port 7497) 40 | 41 | (defonce default-server-log-level :error) 42 | 43 | 44 | (defn next-id-updater 45 | "Returns a function that will take the value from :next-valid-order-id 46 | messages and update the next-id atom accordingly." 47 | [next-id] 48 | (fn [{:keys [type value]}] 49 | (when (= :next-valid-order-id type) 50 | (log/info "Next ID:" value) 51 | (reset! next-id value)))) 52 | 53 | 54 | (defn next-id 55 | "Use this utility function to get the next valid id. 56 | 57 | type should be one of :order :ticker :request 58 | " 59 | [connection] 60 | (let [id @(:next-id connection)] 61 | (swap! (:next-id connection) inc) 62 | id)) 63 | 64 | 65 | (defn error-printer 66 | "Subscribe this function to a connection to print error messages." 67 | [message] 68 | (when (= :error (:type message)) 69 | (println message))) 70 | 71 | 72 | (defn error-logger 73 | "Subscribe this function to a connection to log error messages." 74 | [message] 75 | (when (= :error (:type message)) 76 | (log/error message))) 77 | 78 | 79 | (defn subscribe! 80 | "Adds f to the functions that will get called with every message that is 81 | received from the server. Subscribing the same function more than once has no 82 | effect. 83 | 84 | If an id is passed, keep track of the link between the id and the subscriber 85 | so that it can be removed if the request is canceled. 86 | 87 | Returns the connection. 88 | 89 | Note: Subscribers are called sequentially and are not expected to take a lot 90 | of time to execute. Processing should be sent to a thread to avoid slowing 91 | down the system. 92 | " 93 | ([connection f] 94 | (subscribe! connection f f)) 95 | ([connection id f] 96 | (swap! (:subscribers connection) assoc id f) 97 | connection)) 98 | 99 | 100 | (defmulti unsubscribe! 101 | "Removes the function passed in or the function associated with the id passed 102 | in from the list of functions that will get called with every message from the 103 | server." 104 | (fn [_ x] 105 | (cond 106 | (number? x) :number 107 | (fn? x) :fn 108 | :else :unknown))) 109 | 110 | 111 | (defmethod unsubscribe! :default 112 | [_ x] 113 | (log/error "Don't know how to unsubscribe" x)) 114 | 115 | 116 | (defmethod unsubscribe! :number 117 | [connection id] 118 | (swap! (:subscribers connection) dissoc id) 119 | connection) 120 | 121 | 122 | (defmethod unsubscribe! :fn 123 | [connection f] 124 | (swap! (:subscribers connection) 125 | #(into {} (filter (comp (partial not= f) second) %))) 126 | connection) 127 | 128 | 129 | (defn- message-dispatcher 130 | "Returns a closure responsible for calling every subscriber with messages 131 | passed to it." 132 | [subscribers] 133 | (fn [message] 134 | (doseq [f (vals @subscribers)] 135 | (try 136 | (f message) 137 | (catch Throwable t 138 | (log/error t "Error dispatching" message "to" f)))))) 139 | 140 | 141 | ;;; 142 | ;;; Synchronous calls 143 | ;;; 144 | (defn connect 145 | "Returns a connection that is required for all other calls. 146 | If the connection fails, returns nil. See log in that case. 147 | 148 | client-id identifies this connection. Only one connection can be made per 149 | client-id to the same server.. 150 | 151 | host is the hostname or address of the server running IB Gateway or TWS. 152 | 153 | port is the port configured for that server. 154 | " 155 | ([client-id] 156 | (connect client-id "localhost" default-paper-port)) 157 | ([client-id host port] 158 | (let [subscribers (atom {}) 159 | next-id (atom 1) 160 | id-updater (next-id-updater next-id)] 161 | (swap! subscribers assoc id-updater id-updater ) 162 | (try 163 | (let [wr (wrapper/create (message-dispatcher subscribers)) 164 | ecs (cs/connect wr host port client-id)] 165 | (when-not (= :error default-server-log-level) 166 | (cs/set-server-log-level ecs default-server-log-level)) 167 | {:ecs ecs 168 | :subscribers subscribers 169 | :next-id next-id}) 170 | (catch Exception ex 171 | (log/error "Error trying to connect to " host ":" port ": " ex)))))) 172 | 173 | 174 | (defn disconnect [connection] 175 | (cs/disconnect (:ecs connection))) 176 | 177 | 178 | (defn is-connected? [connection] 179 | (and (:ecs connection) 180 | (cs/is-connected? (:ecs connection)))) 181 | 182 | 183 | (defonce default-handlers 184 | {:data #(log/info "Received:" %) 185 | :end #(log/info "End of the request.") 186 | :error #(log/error "Error: " %)}) 187 | 188 | 189 | (defonce nil-handlers 190 | {}) 191 | 192 | 193 | (defn single-message-handler 194 | "Handler to wait for a single message, call the appropriate handlers and 195 | unsubscribe from the connection when there is nothing else to be done." 196 | [connection handle-type id {:keys [data end error]}] 197 | (fn this [msg] 198 | (cond 199 | (matching-message? handle-type id msg) (do (and data (data (:value msg))) 200 | (and end (end)) 201 | (unsubscribe! connection this)) 202 | 203 | (error-end? id msg) (do (and error (error msg)) 204 | (and end (end)) 205 | (unsubscribe! connection this))))) 206 | 207 | 208 | (defn multiple-messages-handler 209 | "Handler to wait for multiple messages and eventually the appropriate end 210 | message. It will call the appropriate handlers at the time data is received 211 | and unsubscribe from the connection when there is nothing else to be done." 212 | [connection handle-type id {:keys [data end error]}] 213 | (fn this [msg] 214 | (cond 215 | (matching-message? handle-type id msg) (and data (data (:value msg))) 216 | 217 | (request-end? handle-type id msg) (do (and end (end)) 218 | (unsubscribe! connection this)) 219 | 220 | (error-end? id msg) (do (and error (error msg)) 221 | (and end (end)) 222 | (unsubscribe! connection this))))) 223 | 224 | 225 | (defn request-current-time [connection handlers] 226 | (subscribe! connection 227 | (single-message-handler connection :current-time nil handlers)) 228 | (cs/request-current-time (:ecs connection))) 229 | 230 | 231 | (defn request-market-data 232 | "Returns the ticker-id that can be used to cancel the request." 233 | ([connection contract handlers] 234 | (request-market-data connection contract "" false handlers)) 235 | ([connection contract tick-list snapshot? handlers] 236 | (let [ticker-id (next-id connection)] 237 | (subscribe! connection ticker-id 238 | (multiple-messages-handler connection :tick ticker-id handlers)) 239 | (cs/request-market-data (:ecs connection) 240 | ticker-id contract tick-list snapshot?) 241 | ticker-id))) 242 | 243 | 244 | (defn cancel-market-data [connection ticker-id] 245 | (cs/cancel-market-data (:ecs connection) ticker-id) 246 | (unsubscribe! connection ticker-id)) 247 | 248 | 249 | (defn calculate-implied-vol 250 | "Returns the ticker-id that can be used to cancel the request." 251 | [connection contract option-price underlying-price handlers] 252 | (let [ticker-id (next-id connection)] 253 | (subscribe! connection ticker-id 254 | (single-message-handler connection :tick ticker-id handlers)) 255 | (cs/calculate-implied-volatility (:ecs connection) 256 | ticker-id contract 257 | option-price underlying-price) 258 | ticker-id)) 259 | 260 | 261 | (defn cancel-calculate-implied-vol 262 | [connection ticker-id] 263 | (cs/cancel-calculate-implied-volatility (:ecs connection) ticker-id) 264 | (unsubscribe! connection ticker-id)) 265 | 266 | 267 | (defn calculate-option-price 268 | "Returns the ticker-id that can be used to cancel the request." 269 | [connection contract volatility underlying-price handlers] 270 | (let [ticker-id (next-id connection)] 271 | (subscribe! connection ticker-id 272 | (single-message-handler connection :tick ticker-id handlers)) 273 | (cs/calculate-option-price (:ecs connection) 274 | ticker-id contract 275 | volatility underlying-price) 276 | ticker-id)) 277 | 278 | 279 | (defn cancel-calculate-option-price 280 | [connection ticker-id] 281 | (cs/cancel-calculate-option-price (:ecs connection) ticker-id) 282 | (unsubscribe! connection ticker-id)) 283 | 284 | 285 | 286 | (defn order-monitor-handler 287 | "Listen for messages about the order and forwards them to the :data handler 288 | while waiting for the order to be filled or canceled and call the end 289 | handler." 290 | [connection ord-id {:keys [data end error]}] 291 | (fn this [{:keys [type id order-id value] :as msg}] 292 | (let [order-status (= type :order-status) 293 | done (#{:filled :cancelled} (:status value)) 294 | this-order (= order-id ord-id)] 295 | (cond 296 | (and order-status 297 | this-order) (do (and data (data value)) 298 | (when done 299 | (and end (end)) 300 | (unsubscribe! connection this))) 301 | 302 | (and (= id order-id) 303 | (error-end? msg)) (do (and error (error msg)) 304 | (and end (end)) 305 | (unsubscribe! connection this)))))) 306 | 307 | 308 | (defn place-and-monitor-order 309 | "Returns the order-id that can be used to cancel or modify the order." 310 | [connection contract order handlers] 311 | (let [order-id (next-id connection)] 312 | (subscribe! connection order-id 313 | (order-monitor-handler connection order-id handlers)) 314 | (cs/place-order (:ecs connection) 315 | order-id contract (assoc order :order-id order-id)) 316 | order-id)) 317 | 318 | 319 | (defn modify-order [connection order-id contract order] 320 | (cs/place-order (:ecs connection) 321 | order-id contract (assoc order :order-id order-id)) 322 | order-id) 323 | 324 | 325 | (defn cancel-order [connection order-id] 326 | (cs/cancel-order (:ecs connection) order-id) 327 | (unsubscribe! connection order-id)) 328 | 329 | 330 | (defn request-open-orders [connection handlers] 331 | (subscribe! connection 332 | (multiple-messages-handler connection 333 | :open-order nil handlers)) 334 | (cs/request-open-orders (:ecs connection)) 335 | connection) 336 | 337 | 338 | (defn cancel-all-orders 339 | "This will cancel all orders including the ones entered in TWS." 340 | [connection] 341 | (cs/request-global-cancel (:ecs connection)) 342 | connection) 343 | 344 | 345 | (defn request-positions [connection handlers] 346 | (subscribe! connection (multiple-messages-handler connection 347 | :position nil handlers)) 348 | (cs/request-positions (:ecs connection)) 349 | connection) 350 | 351 | 352 | (defn cancel-positions [connection] 353 | (cs/cancel-positions (:ecs connection)) 354 | connection) 355 | 356 | 357 | (defn request-contract-details [connection contract handlers] 358 | (let [request-id (next-id connection)] 359 | (subscribe! connection request-id 360 | (multiple-messages-handler connection 361 | :contract-details request-id handlers)) 362 | (cs/request-contract-details (:ecs connection) request-id contract) 363 | request-id)) 364 | 365 | 366 | (defn request-historical-data 367 | ([connection contract end 368 | duration duration-unit bar-size bar-size-unit 369 | what-to-show use-regular-trading-hours? handlers] 370 | (let [request-id (next-id connection)] 371 | (subscribe! connection request-id 372 | (multiple-messages-handler connection :price-bar request-id handlers)) 373 | (cs/request-historical-data (:ecs connection) request-id contract 374 | end duration duration-unit bar-size bar-size-unit 375 | what-to-show use-regular-trading-hours?) 376 | request-id)) 377 | ([connection contract end 378 | duration duration-unit bar-size bar-size-unit 379 | what-to-show handlers] 380 | (request-historical-data connection contract end duration duration-unit 381 | bar-size bar-size-unit what-to-show true handlers)) 382 | ([connection contract end 383 | duration duration-unit bar-size bar-size-unit handlers] 384 | (request-historical-data connection contract end duration duration-unit 385 | bar-size bar-size-unit :trades true handlers))) 386 | 387 | 388 | (defn request-fundamental-data 389 | [connection contract report-type handlers] 390 | (let [request-id (next-id connection)] 391 | (subscribe! connection request-id 392 | (multiple-messages-handler connection :fundamental-data request-id 393 | handlers)) 394 | (cs/request-fundamental-data (:ecs connection) request-id contract report-type) 395 | request-id)) 396 | 397 | 398 | (defn cancel-fundamental-data 399 | [connection request-id] 400 | (cs/cancel-fundamental-data (:ecs connection) request-id) 401 | (unsubscribe! connection request-id)) 402 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ib-re-actor 2 | 3 | A clojure friendly wrapper around the Interactive Brokers java API. 4 | 5 | ## Usage 6 | 7 | ### Installing twsapi locally 8 | 9 | At this time, IB does not distribute the TWSAPI on maven central so you have to 10 | download it manually from 11 | [here](http://interactivebrokers.github.io/downloads/twsapi_macunix.971.01.jar) and 12 | install it locally using the helper script. 13 | 14 | ``` 15 | scripts/twsapi-971 ~/somewhere/twsapi_macunix.971.01.jar 16 | ``` 17 | 18 | ### Using ib-re-actor 19 | 20 | In project.clj: 21 | 22 | ```clojure 23 | (project my.project "0.0.0" 24 | :dependencies [[ib-re-actor "0.1.10"] 25 | [twsapi "9.71.01"]] 26 | ...) 27 | ``` 28 | 29 | You can use ib-re-actor either with IB Trader Workstation or with IB 30 | Gateway. Personally, I have the gateway running on a linux server that 31 | I use VNC to connect to when I need to start/restart the gateway 32 | component. I then run programs on that machine that connect to it 33 | locally. It would be nice if there were a way to run the gateway 34 | without X and without having to authenticate, but alas, that's not how 35 | it works. 36 | 37 | Since ib-re-actor is basically a wrapper around Interactive Brokers' 38 | java API, [the documentation for that library][1] is often useful to have 39 | around. It describes all the codes, order types, and the types of 40 | errors you might get when using it. 41 | 42 | ## Examples 43 | 44 | ### Connecting 45 | 46 | ib-react-or maintains a connection to the interactive brokers gateway 47 | that you can share amongst many handler functions. To establish a connection: 48 | 49 | ```clojure 50 | user> (connect) 51 | # 52 | ``` 53 | 54 | In order to get called back when a message is received from the 55 | gateway, use the subscribe function: 56 | 57 | ```clojure 58 | user> (subscribe prn) 59 | #)> 60 | ``` 61 | 62 | To see the callback in action, you can call something like 63 | `request-current-time`. This sends a message to the gateway, then to 64 | Interactive Broker servers, and eventually returns the current time to 65 | our callback function via a :current-time message: 66 | 67 | ```clojure 68 | user=> (request-current-time) 69 | #> 70 | user=> {:type :current-time, :value #} 71 | ``` 72 | 73 | Note that the messages is rudely placed after your prompt (or if you 74 | are using nrepl or swank in emacs, you won't see any response.) This 75 | is because the message is dispatched on an agent thread. (If you are 76 | using emacs, it's actually less rude, because your message will be 77 | visible in the \*nrepl-server\* buffer, or whatever the equivalent is 78 | in swank.) 79 | 80 | To disconnect, simply call `disconnect`: 81 | 82 | ```clojure 83 | user=> (disconnect) 84 | #> 85 | user=> 86 | ``` 87 | 88 | Any commands you issue after that will get back a "Not connected" 89 | error message: 90 | 91 | ```clojure 92 | user=> (disconnect) 93 | #> 94 | user=> (request-current-time) 95 | #> 96 | {:type :error, :request-id -1, :code 504, :message "Not connected"} 97 | user=> 98 | ``` 99 | 100 | Note that there can be only one connection to the Interactive Brokers 101 | gateway or TWS for a given client ID, so if you are writing an 102 | application that makes multiple connections (from different 103 | processes), you will want to come up with a way to keep the client IDs unique. 104 | 105 | ### Errors 106 | 107 | Errors generally come back from the gateway in a message. They can be 108 | request specific, in which case they will have a non-negative 109 | `:request-id`, connection wide, in which case the `:request-id` will 110 | be -1, and sometimes they may include an exception. 111 | 112 | ### Requesting Contract Information 113 | 114 | All tradeable instruments are referred to as "contracts" in the 115 | API documentation. Contracts are divided into a few types: 116 | 117 | * :equity : stock, common stock, preferred stock 118 | * :option : option contracts on stocks or other instruments 119 | * :future : futures contracts on commodities 120 | * :index : informational symbols, such as the value of the S&P 121 | 500. These are generally not tradeable, but you can use the 122 | same API functions to get information about them as you 123 | would for tradeable instruments. 124 | * :future-option : options on futures contracts 125 | * :cash, :bag: ??? 126 | 127 | When requesting information about the contract, you need to specify a 128 | symbol to lookup. Your options are the `:symbol` key for the general 129 | symbol, or `:local-symbol` for an exchange specific symbol. Generally, 130 | you also want to specify an `:exchange`, and maybe a `:currency` as 131 | well, unless you are not sure and want more results to look for. 132 | 133 | ```clojure 134 | user> (request-contract-details {:symbol "AAPL" :type :equity}) 135 | 6 136 | user> 137 | ;;; many, many results 138 | ... 139 | ;;; example: APPL trading in Mexico: 140 | 141 | {:type :contract-details, :value {:next-option-partial false, 142 | :time-zone-id "CTT", 143 | :long-name "APPLE INC", 144 | :subcategory "Computers", 145 | :liquid-hours "20121220:0830-1500;20121221:0830-1500", 146 | ... 147 | :order-types [:ACTIVETIM :ADJUST :ALERT :ALLOC ...] 148 | :valid-exchanges ["MEXI"], 149 | :summary {:symbol "AAPL", :currency "MXN", :contract-id 38708077, 150 | :primary-exchange "MEXI", 151 | :local-symbol "AAPL", 152 | :type :equity, 153 | :exchange "MEXI"}, 154 | :market-name "AAPL", 155 | :category "Computers" ...}, 156 | :request-id 6} 157 | ... 158 | {:type :contract-details-end, :request-id 6} 159 | 160 | ;;; more specifically, if we were interested in trading AAPL on 161 | ;;; ISLAND: 162 | 163 | user> (request-contract-details {:symbol "AAPL" :type :equity :exchange "ISLAND"}) 164 | 165 | ;;; only gets the one match 166 | 167 | ``` 168 | 169 | You can see all the valid exchanges for a security in the results from 170 | a broad search and then be more specific when you actually want to 171 | trade it. 172 | 173 | As you can see, the response contains a `:local-symbol` which is the 174 | same as the symbol we requested. Even when the local-symbol and symbol 175 | don't match, you can use `:symbol` and just specify the exchange: 176 | 177 | ```clojure 178 | user> (request-contract-details {:symbol "BP" :type :equity}) 179 | 180 | ;;; lot's of matches 181 | ... 182 | ;;; say we only wanted this one: 183 | {:type :contract-details, :request-id 11, :value { ... 184 | :long-name "BANCO POPOLARE SCARL", ... 185 | :valid-exchanges ["SMART" "BVME" "FWB" "MIBSX" "SWB"], ... 186 | :summary {..., :type :equity, :currency "EUR", 187 | :primary-exchange "BVME", 188 | :local-symbol "B8Z", 189 | :exchange "SWB", 190 | :symbol "BP", ...}, ...}} 191 | 192 | ;;; be more specific 193 | user> (request-contract-details {:symbol "BP" :exchange "SWB" :type :equity}) 194 | 195 | ;;; only gets the one match 196 | {... :value {... :long-name "BANCO POPLARE SCARL" ...} ...} 197 | 198 | ;;; or: 199 | user> (request-contract-details {:local-symbol "B8Z" :type :equity}) 200 | 201 | ;;; actually gets 2 matches, because Banco Poplare's local symbol is 202 | ;;; the same on both the SWB (Stuttgart) and FWB (Frankfurt) 203 | ;;; exchanges. 204 | 205 | ``` 206 | 207 | For futures, I usually find it works best to use a local symbol with 208 | a built in expiration: 209 | 210 | ```clojure 211 | user> (request-contract-details {:local-symbol "ESH3" :type :future}) 212 | 213 | {... :value {... :long-name "E-mini S&P 500", :contract-month "201303", 214 | :summary {:multiplier 50.0, :expiry #, 215 | :type :future, :currency "USD", :local-symbol "ESH3", 216 | :exchange "GLOBEX", :symbol "ES", ..., :contract-id 98770297}, 217 | :market-name "ES", ...}} 218 | 219 | user> (request-contract-details {:local-symbol "ZN DEC 12" :type :future}) 220 | 221 | {..., :value {... :long-name "10 Year US Treasury Note", :contract-month "201212", 222 | :summary {:multiplier 1000.0, :expiry #, 223 | :type :future, :currency "USD", :local-symbol "ZN DEC 12", 224 | :exchange "ECBOT", :symbol "ZN", :contract-id 94977350}, 225 | :market-name "ZN"}} 226 | 227 | ;;; alternatively, specify the :symbol and an expiry: 228 | user> (request-contract-details {:symbol "NQ" :expiry (date-time 2012 12) :type :future}) 229 | 230 | ;;; same result 231 | 232 | ``` 233 | 234 | You can also use `:contract-id`, which is a unique identifier assigned 235 | by Interactive Brokers to identify securities: 236 | 237 | ```clojure 238 | user> (request-contract-details {:contract-id 98770297}) 239 | 240 | {... :value {... :long-name "E-mini S&P 500", :contract-month "201303", 241 | :summary {:multiplier 50.0, :expiry #, 242 | :type :future, :currency "USD", :local-symbol "ESH3", 243 | :exchange "GLOBEX", :symbol "ES", ..., :contract-id 98770297}, 244 | :market-name "ES", ...}} 245 | 246 | ``` 247 | 248 | ### Requesting Historical Data 249 | 250 | To get historical bars, use the `request-historical-data` function: 251 | 252 | ```clojure 253 | ;;; '1' is the request-id that will be part of all messages in 254 | ;;; response to this request 255 | user> (request-historical-data 1 {:symbol "AAPL" :type :equity :exchange "ISLAND"} 256 | (date-time 2012 12 18 20) 1 :day 1 :hour) 257 | 258 | {:WAP 524.187, :close 521.69, :has-gaps? false, :trade-count 4538, :low 521.27, :type :price-bar, :time #, :open 524.88, :high 526.35, :volume 8260, :request-id 1} 259 | ... 260 | {:WAP 529.905, :close 530.79, :has-gaps? false, :trade-count 2563, :low 527.79, :type :price-bar, :time #, :open 530.27, :high 531.64, :volume 3293, :request-id 1} 261 | {:type :price-bar-complete, :request-id 1} 262 | 263 | ``` 264 | 265 | Note, all date-times are in UTC unless otherwise noted. 266 | 267 | Interactive Brokers throttles historical data requests. The 268 | restrictions at the time this was written were: 269 | - bar size <= 30 seconds: 6 months 270 | - each request must be for less than 2000 bars 271 | - no identical requests within 15 seconds 272 | - no making more than 6 requests for the same 273 | contract+exchange+tick-type within 2 seconds 274 | - no more than 60 historical data requests in a 10 minute period 275 | 276 | In order to remain within these limits we must throttle requests for 277 | large amounts of data. One way to do this is to break up the requests 278 | such that you are requesting less than 2000 bars every 10 279 | seconds. This satisfies the second requirement above and insures that 280 | you will have a maximum of 60 requests in a 10 minute period, 281 | satisfying the last requirement. 282 | 283 | For example, let's say we want 1 second bars for an entire day for DOW 284 | minis. 2000 seconds is about 33 minutes. YMZ2 trades almost all day, 285 | but let's say we want to start when it gets liquid all the way until 286 | the next time it stops trading: 287 | 288 | ```clojure 289 | user> (def ymz2 {:type :future :local-symbol "YM DEC 12" 290 | :exchange "ECBOT"} 291 | #'user/ymz2 292 | user> (-> (get-contract-details ymz2) first :value 293 | (select-keys [:liquid-hours :trading-hours]) 294 | pprint) 295 | {:trading-hours "20121021:CLOSED;20121023:1700-1515", 296 | :liquid-hours "20121021:CLOSED;20121023:0830-1515"} 297 | ``` 298 | 299 | So let's request 1 second bars from 8:30 to 15:15 EST (13:30 to 20:15 300 | UTC). That's 9 hours and 15 minutes, or a total of 19 requests. 301 | 302 | This example breaks the requested period up into retrievable chunks. 303 | 304 | ```clojure 305 | user> (def prices (atom [])) 306 | #'user/prices 307 | 308 | ;; we can subscribe this function with request-id curried in 309 | user> (defn store-price [request-id msg] 310 | (when (and (= request-id (:request-id msg)) 311 | (= :price-bar (:type msg))) 312 | (swap! prices conj msg))) 313 | #'user/store-price 314 | user> (def end-times (->> (iterate #(plus % (secs 2000)) (date-time 2012 12 18 11 30)) 315 | (take 19))) 316 | #'user/end-times 317 | user> (prn (first end-times) (last end-times)) 318 | # # 319 | nil 320 | user> 321 | ``` 322 | 323 | In order to avoid pacing violations, we will make each request, then 324 | sleep for 10 seconds: 325 | 326 | ```clojure 327 | ;;; Get a unique request id 328 | user> (def my-request-id (get-request-id)) 329 | #'user/my-request-id 330 | user> (subscribe (partial store-price my-request-id)) 331 | # 332 | user> (doseq [t end-times] 333 | (request-historical-data my-request-id ymz2 t 2000 :seconds 1 :seconds) 334 | (Thread/sleep 10000)) 335 | ``` 336 | 337 | ### Requesting Real Time Data 338 | 339 | FIXME: write this 340 | 341 | ### Market Scanners 342 | 343 | FIXME: write this 344 | 345 | ### Orders 346 | 347 | To see all the active orders on a connection, use the 348 | `request-account-updates` function. If `subscribe?` is true, a message 349 | will be sent each time any attribute of the account changes. An 350 | `:account-download-end` message will be sent when all the changes are 351 | made. 352 | 353 | ```clojure 354 | 355 | ``` 356 | 357 | ### Account Management 358 | 359 | FIXME: write this 360 | 361 | ### Synchronous Wrappers 362 | 363 | There are several wrapper functions in the `ib-re-actor.synchronous` 364 | namespace that make the process of interacting with the gateway 365 | synchronous and more interactive. This is useful when doing 366 | exploratory coding in the REPL, where you don't want the hassle of 367 | creating functions and managing their subscriptions for simple requests. 368 | 369 | ```clojure 370 | ;;; wrap request-historical-data 371 | user> (get-historical-data {:symbol "AAPL" :type :equity :exchange "ISLAND"} 372 | (date-time 2012 12 20 20) 1 :day 1 :hour :trades true) 373 | ({:has-gaps? false, :volume 3507, :trade-count 2687, :close 524.77, 374 | :low 521.14, :high 525.41, :open 522.58, :time #} {:has-gaps? false, :volume 4545 376 | ... 377 | 378 | ;;; wrap request-current-time 379 | user> (get-time) 380 | # 381 | 382 | ;;; wrap request-contract-details 383 | user> (get-contract-details {:symbol "AAPL" :type :equity :exchange "ISLAND"}) 384 | ({:type :contract-details, :request-id 38, :value {:next-option-partial false, 385 | :time-zone-id "EST", :underlying-contract-id 0, :price-magnifier 1, 386 | :industry "Technology", :trading-hours "20121220:0700-2000;20121221:0700-2000", 387 | :long-name "APPLE INC", :convertible? false, ... 388 | 389 | ;;; wrap getting the current price for a security (request-market-data) 390 | user> (get-current-price {:symbol "AAPL" :type :equity :exchange "ISLAND"}) 391 | {:open-tick 530.15, :bid-price -1.0, :close-price 526.31, :last-size 3, :low 518.88, :ask-size 0, :bid-size 0, :last-timestamp #, :last-price 520.17, :ask-price -1.0, :high 530.2, :volume 170569} 392 | 393 | ;;; wrap executing an order and blocking until it's filled 394 | user> (execute-order {:symbol "AAPL" :type :equity :exchange "ISLAND"} 395 | {:transmit? true :action :buy :type :market :quantity 100 396 | :outside-regular-traiding-hours? true}) 397 | 398 | ; FIXME: make an example while the market is open 399 | 400 | ;;; wrap getting all open orders (request-open-orders) 401 | user> (get-open-orders) 402 | ({:order-state {:maximum-commission 1.7976931348623157E308, :minimum-commission 1.7976931348623157E308, :commission 1.7976931348623157E308, :equity-with-loan "1.7976931348623157E308", :maintenance-margin "1.7976931348623157E308", :initial-margin "1.7976931348623157E308", :status :pre-submitted}, :order {:time-in-force :day, :order-id 3, :client-id 101, :discretionary-amount 0.0, :action :buy, :quantity 100, :sweep-to-fill? false, :limit-price 0.0, :outside-regular-trading-hours? false, :transmit? true, :stop-price 0.0, :hidden? false, :type :market, :all-or-none? false, :block-order? false, :permanent-id 1644329200}, :contract {:put-call-right :unknown, :include-expired? false, :type :equity, :currency "USD", :local-symbol "AAPL", :exchange "ISLAND", :symbol "AAPL", :contract-id 265598}, :order-id 3}) 403 | ``` 404 | 405 | ### Other Things 406 | 407 | #### Implementation notes 408 | 409 | The IB API appears to differentiate between types of ids such as ticker id, 410 | order id and request id. At some point, this library kept them independent but a 411 | problem occurs when an error is received. Indeed, errors specify an id but not 412 | the type of id. It is thus impossible to know the error concerns which of the 413 | requests. In order to alleviate this problem ids are not reused. 414 | 415 | ## License 416 | 417 | Copyright (C) 2011 Chris Bilson, Jean-Sebastien A. Beaudry 418 | 419 | Distributed under the Eclipse Public License, the same as Clojure. 420 | 421 | [1]: http://www.interactivebrokers.com/en/software/api/api.htm 422 | -------------------------------------------------------------------------------- /src/ib_re_actor/wrapper.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.wrapper 2 | (:require 3 | [clojure.tools.logging :as log] 4 | [clojure.xml :as xml] 5 | [ib-re-actor.mapping :refer [->map]] 6 | [ib-re-actor.translation :refer [boolean-account-value? integer-account-value? numeric-account-value? translate]])) 7 | 8 | 9 | (defn- get-stack-trace [ex] 10 | (let [sw (java.io.StringWriter.) 11 | pw (java.io.PrintWriter. sw)] 12 | (.printStackTrace ex pw) 13 | (.toString sw))) 14 | 15 | 16 | (defn- log-exception 17 | ([ex msg] 18 | (log/error msg ": " (.getMessage ex)) 19 | (log/error "Stack Trace: " (get-stack-trace ex))) 20 | ([ex] 21 | (log-exception ex "Error"))) 22 | 23 | 24 | (defn- is-finish? [date-string] 25 | (.startsWith date-string "finished")) 26 | 27 | 28 | (defn matching-message? [handle-type id 29 | {:keys [type request-id order-id ticker-id] :as message}] 30 | (and (= handle-type type) 31 | (or (nil? id) 32 | (= id (or request-id order-id ticker-id))))) 33 | 34 | 35 | (defn warning-code? 36 | "One would think that warnings start at 2100 but there are error codes 37 | starting at 10000." 38 | [code] 39 | (<= 2100 code 2200)) 40 | 41 | 42 | (defn error-code? [code] 43 | (complement warning-code?)) 44 | 45 | 46 | (defn connection-error-code? [code] 47 | (#{504 ; Not connected 48 | 1100 ; Connectivity between IB and TWS has been lost 49 | } code)) 50 | 51 | 52 | (defn warning? 53 | "A message is a warning if it has :type :error and has a code that is a 54 | warning code. 55 | 56 | IB also sends warnings with error codes but with a message containing 57 | \"Warning:\". For example, when you submit an order outside the trading hours 58 | you get an error code 399 but the message indicates that it is only a warning. 59 | " 60 | [{:keys [type code message] :as msg}] 61 | (and (= :error type) 62 | (or (and code (warning-code? code)) 63 | (and message (re-seq #"Warning:" message))))) 64 | 65 | 66 | (defn error? [{:keys [type] :as message}] 67 | (and (= :error type) 68 | (not (warning? message)))) 69 | 70 | 71 | (defn error-end? 72 | "Determines if a message is an error message that ends a request. 73 | 74 | id is the request, order or ticker id for the request." 75 | ([msg] 76 | (error-end? nil msg)) 77 | ([req-id {:keys [type code id] :as msg}] 78 | (and (error? msg) 79 | (= req-id id)))) 80 | 81 | 82 | (def end-message-type {:tick :tick-snapshot-end 83 | :open-order :open-order-end 84 | :update-account-value :account-download-end 85 | :update-portfolio :account-download-end 86 | :account-summary :account-summary-end 87 | :position :position-end 88 | :contract-details :contract-details-end 89 | :execution-details :execution-details-end 90 | :price-bar :price-bar-complete 91 | :scan-result :scan-end}) 92 | 93 | 94 | (defn request-end? 95 | "Predicate to determine if a message indicates a series of responses for a 96 | request is done. 97 | 98 | message-type is the type of the data coming in. For example: :price-bar 99 | or :open-order." 100 | [message-type req-id 101 | {:keys [type request-id order-id ticker-id] :as msg}] 102 | (and (= type (end-message-type message-type)) 103 | (or (nil? req-id) 104 | (= req-id (or request-id order-id ticker-id))))) 105 | 106 | 107 | (defn- dispatch-message [cb msg] 108 | (cb msg)) 109 | 110 | 111 | (defn create 112 | "Creates a wrapper that flattens the Interactive Brokers EWrapper interface, 113 | calling a single function (cb) with maps that all have a :type to indicate 114 | what type of messages was received, and the massaged parameters from the 115 | event. 116 | 117 | See: https://www.interactivebrokers.com/en/software/api/api.htm 118 | " 119 | [cb] 120 | (reify 121 | com.ib.client.EWrapper 122 | 123 | ;;; Connection and Server 124 | (currentTime [this time] 125 | (dispatch-message cb {:type :current-time 126 | :value (translate :from-ib :date-time time)})) 127 | 128 | (^void error [this ^int id ^int errorCode ^String message] 129 | (dispatch-message cb {:type :error :id id :code errorCode 130 | :message message})) 131 | 132 | (^void error [this ^Exception ex] 133 | (dispatch-message cb {:type :error :exception (.toString ex)})) 134 | 135 | (^void error [this ^String message] 136 | (dispatch-message cb {:type :error :message message})) 137 | 138 | (connectionClosed [this] 139 | (dispatch-message cb {:type :connection-closed})) 140 | 141 | ;;; Market Data 142 | (tickPrice [this tickerId field price canAutoExecute] 143 | (dispatch-message cb {:type :tick :ticker-id tickerId 144 | :value {:field (translate :from-ib :tick-field-code field) 145 | :value price 146 | :can-auto-execute? (= 1 canAutoExecute)}})) 147 | 148 | (tickSize [this tickerId field size] 149 | (dispatch-message cb {:type :tick :ticker-id tickerId 150 | :value {:field (translate :from-ib :tick-field-code field) 151 | :value size}})) 152 | 153 | (tickOptionComputation [this tickerId field impliedVol delta optPrice 154 | pvDividend gamma vega theta undPrice] 155 | (dispatch-message cb {:type :tick :ticker-id tickerId 156 | :value {:field (translate :from-ib :tick-field-code field) 157 | :value {:implied-volatility impliedVol 158 | :option-price optPrice 159 | :pv-dividends pvDividend 160 | :underlying-price undPrice 161 | :delta delta :gamma gamma 162 | :theta theta :vega vega}}})) 163 | 164 | (tickGeneric [this tickerId tickType value] 165 | (dispatch-message cb {:type :tick :ticker-id tickerId 166 | :value {:field (translate :from-ib :tick-field-code tickType) 167 | :value value}})) 168 | 169 | (tickString [this tickerId tickType value] 170 | (let [field (translate :from-ib :tick-field-code tickType)] 171 | (dispatch-message cb {:type :tick :ticker-id tickerId 172 | :value {:field field 173 | :value (case field 174 | :last-timestamp 175 | (translate :from-ib :date-time value) 176 | 177 | value)}}))) 178 | 179 | (tickEFP [this tickerId tickType basisPoints formattedBasisPoints 180 | impliedFuture holdDays futureExpiry dividendImpact dividendsToExpiry] 181 | (dispatch-message cb {:type :tick :ticker-id tickerId 182 | :value {:field (translate :from-ib :tick-field-code tickType) 183 | :basis-points basisPoints 184 | :formatted-basis-points formattedBasisPoints 185 | :implied-future impliedFuture :hold-days holdDays 186 | :future-expiry futureExpiry 187 | :dividend-impact dividendImpact 188 | :dividends-to-expiry dividendsToExpiry}})) 189 | 190 | (tickSnapshotEnd [this reqId] 191 | (dispatch-message cb {:type :tick-snapshot-end :request-id reqId})) 192 | 193 | (marketDataType [this reqId type] 194 | (dispatch-message cb {:type :market-data-type :request-id reqId 195 | :value (translate :from-ib :market-data-type type)})) 196 | 197 | ;;; Orders 198 | (orderStatus [this orderId status filled remaining avgFillPrice permId 199 | parentId lastFillPrice clientId whyHeld] 200 | (dispatch-message cb {:type :order-status :order-id orderId 201 | :value {:status (translate :from-ib :order-status status) 202 | :filled filled :remaining remaining 203 | :average-fill-price avgFillPrice 204 | :permanent-id permId :parent-id parentId 205 | :last-fill-price lastFillPrice :client-id clientId 206 | :why-held whyHeld}})) 207 | 208 | (openOrder [this orderId contract order orderState] 209 | (dispatch-message cb {:type :open-order :order-id orderId 210 | :value {:contract (->map contract) 211 | :order (->map order) 212 | :order-state (->map orderState)}})) 213 | 214 | (openOrderEnd [this] 215 | (dispatch-message cb {:type :open-order-end})) 216 | 217 | (nextValidId [this orderId] 218 | (dispatch-message cb {:type :next-valid-order-id 219 | :value orderId})) 220 | 221 | ;; In newer docs 222 | (deltaNeutralValidation [this reqId underComp] 223 | (dispatch-message cb {:type :delta-neutral-validation :request-id reqId 224 | ;;TODO: Should we return the underComp directly here? 225 | :value {:underlying-component (->map underComp)}})) 226 | 227 | ;;; Account and Portfolio 228 | (updateAccountValue [this key value currency accountName] 229 | (let [avk (translate :from-ib :account-value-key key) 230 | val (cond 231 | (integer-account-value? avk) (Integer/parseInt value) 232 | (numeric-account-value? avk) (Double/parseDouble value) 233 | (boolean-account-value? avk) (Boolean/parseBoolean value) 234 | :else value)] 235 | (dispatch-message cb {:type :update-account-value 236 | :value {:key avk :value val 237 | :currency currency :account accountName}}))) 238 | 239 | (updatePortfolio [this contract position marketPrice marketValue averageCost 240 | unrealizedPNL realizedPNL accountName] 241 | (dispatch-message cb {:type :update-portfolio 242 | :value {:contract (->map contract) 243 | :position position :market-price marketPrice 244 | :market-value marketValue :average-cost averageCost 245 | :unrealized-gain-loss unrealizedPNL 246 | :realized-gain-loss realizedPNL 247 | :account accountName}})) 248 | 249 | (updateAccountTime [this timeStamp] 250 | (dispatch-message cb {:type :update-account-time 251 | :value (translate :from-ib :time-of-day timeStamp)})) 252 | 253 | (accountDownloadEnd [this account-code] 254 | (dispatch-message cb {:type :account-download-end :account-code account-code})) 255 | 256 | (accountSummary [this reqId account tag value currency] 257 | (dispatch-message cb {:type :account-summary :request-id reqId 258 | :value {:account account 259 | :tag tag 260 | :value value :currency currency}})) 261 | 262 | (accountSummaryEnd [this reqId] 263 | (dispatch-message cb {:type :account-summary-end :request-id reqId})) 264 | 265 | (position [this account contract pos avgCost] 266 | (dispatch-message cb {:type :position 267 | :value {:account account 268 | :contract (->map contract) 269 | :position pos 270 | :average-cost avgCost}})) 271 | 272 | (positionEnd [this] 273 | (dispatch-message cb {:type :position-end})) 274 | 275 | ;;; Contract Details 276 | (contractDetails [this requestId contractDetails] 277 | (let [{:keys[trading-hours liquid-hours time-zone-id 278 | security-id-list] :as m} (->map contractDetails) 279 | th (translate :from-ib :trading-hours [time-zone-id trading-hours]) 280 | lh (translate :from-ib :trading-hours [time-zone-id liquid-hours]) 281 | sids (translate :from-ib :security-id-list security-id-list)] 282 | (dispatch-message cb {:type :contract-details :request-id requestId 283 | :value (merge m 284 | (when th {:trading-hours th}) 285 | (when lh {:liquid-hours lh}) 286 | (when sids {:security-id-list sids}))}))) 287 | 288 | (contractDetailsEnd [this requestId] 289 | (dispatch-message cb {:type :contract-details-end :request-id requestId})) 290 | 291 | (bondContractDetails [this requestId contractDetails] 292 | (dispatch-message cb {:type :contract-details :request-id requestId 293 | :value (->map contractDetails)})) 294 | 295 | ;;; Execution Details 296 | (execDetails [this requestId contract execution] 297 | (dispatch-message cb {:type :execution-details :request-id requestId 298 | :value {:contract (->map contract) 299 | :value (->map execution)}})) 300 | 301 | (execDetailsEnd [this requestId] 302 | (dispatch-message cb {:type :execution-details-end :request-id requestId})) 303 | 304 | (commissionReport [this commissionReport] 305 | (dispatch-message cb {:type :commission-report 306 | :value (->map commissionReport)})) 307 | 308 | ;;; Market Depth 309 | (updateMktDepth [this tickerId position operation side price size] 310 | (dispatch-message cb {:type :update-market-depth :ticker-id tickerId 311 | :value {:position position 312 | :operation (translate :from-ib 313 | :market-depth-row-operation 314 | operation) 315 | :side (translate :from-ib :market-depth-side side) 316 | :price price :size size}})) 317 | 318 | (updateMktDepthL2 [this tickerId position marketMaker operation side price size] 319 | (dispatch-message cb {:type :update-market-depth-level-2 :ticker-id tickerId 320 | :value {:position position 321 | :market-maker marketMaker 322 | :operation (translate :from-ib 323 | :market-depth-row-operation 324 | operation) 325 | :side (translate :from-ib :market-depth-side side) 326 | :price price :size size}})) 327 | 328 | ;;; News Bulletin 329 | (updateNewsBulletin [this msgId msgType message origExchange] 330 | (dispatch-message cb {:type :news-bulletin 331 | :id msgId 332 | :value {:type (condp = msgType 333 | 0 :news-bulletin 334 | 1 :exchange-unavailable 335 | 2 :exchange-available) 336 | :message message 337 | :exchange origExchange}})) 338 | 339 | ;;; Financial Advisors 340 | (managedAccounts [this accountsList] 341 | (dispatch-message cb {:type :managed-accounts 342 | :value (->> (.split accountsList ",") (map #(.trim %)) vec)})) 343 | 344 | (receiveFA [this faDataType xml] 345 | (dispatch-message cb {:type (translate :from-ib 346 | :financial-advisor-data-type faDataType) 347 | :value xml})) 348 | 349 | ;;; Historical Data 350 | (historicalData [this requestId date open high low close volume count wap hasGaps] 351 | (if (is-finish? date) 352 | (dispatch-message cb {:type :price-bar-complete :request-id requestId}) 353 | (dispatch-message cb 354 | {:type :price-bar :request-id requestId 355 | :value {:time (translate :from-ib :timestamp date) 356 | :open open :close close 357 | :high high :low low :volume volume 358 | :trade-count count :WAP wap :has-gaps? hasGaps}}))) 359 | 360 | ;;; Market Scanners 361 | (scannerParameters [this xml] 362 | (dispatch-message cb {:type :scan-parameters :value xml})) 363 | 364 | (scannerData [this requestId rank contractDetails distance benchmark 365 | projection legsStr] 366 | (dispatch-message cb {:type :scan-result :request-id requestId 367 | :value {:rank rank 368 | :contract-details (->map contractDetails) 369 | :distance distance :benchmark benchmark 370 | :projection projection :legs legsStr}})) 371 | 372 | (scannerDataEnd [this requestId] 373 | (dispatch-message cb {:type :scan-end :request-id requestId})) 374 | 375 | ;;; Real Time Bars 376 | (realtimeBar [this requestId time open high low close volume wap count] 377 | (dispatch-message cb {:type :price-bar :request-id requestId 378 | :value {:time (translate :from-ib :date-time time) 379 | :open open :close close 380 | :high high :low low :volume volume 381 | :count count :WAP wap}})) 382 | 383 | ;;; Fundamental Data 384 | (fundamentalData [this requestId xml] 385 | (let [report-xml (xml/parse (java.io.ByteArrayInputStream. (.getBytes xml)))] 386 | (dispatch-message cb {:type :fundamental-data :request-id requestId 387 | :value report-xml}))) 388 | 389 | ;;; Display Groups 390 | (displayGroupList [this reqId groups] 391 | (dispatch-message cb {:type :display-group-list :request-id reqId 392 | :value (->> (.split groups "|") (map #(.trim %)) vec)})) 393 | 394 | (displayGroupUpdated [this reqId contractInfo] 395 | (dispatch-message cb {:type :display-group-updated :request-id reqId 396 | :value {:contract-info contractInfo}})))) 397 | -------------------------------------------------------------------------------- /src/ib_re_actor/client_socket.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.client-socket 2 | "This namespace is a wrapper of the EClientSocket interface of the 3 | InteractiveBrokers (IB) API. 4 | 5 | It marshalls data from clojure to what is expected on the IB side. 6 | 7 | Note that the IB API is asynchronous for the most part and that responses will 8 | be received through the EWrapper. Please refer to the link below to know what 9 | is expected. 10 | 11 | https://www.interactivebrokers.com/en/software/api/api.htm 12 | " 13 | (:require 14 | [ib-re-actor.mapping :refer [map->]] 15 | [ib-re-actor.translation :refer [translate]]) 16 | (:import 17 | (com.ib.client EClientSocket))) 18 | 19 | ;;; 20 | ;;; Connection and Server 21 | ;;; 22 | (defn connect 23 | "This function must be called before any other. There is no feedback 24 | for a successful connection, but a subsequent attempt to connect 25 | will return the message 'Already connected.' 26 | 27 | wrapper is an implementation of the EWrapper interface. 28 | 29 | host is the hostname running IB Gateway or TWS. 30 | 31 | port is the port IB Gateway / TWS is running on. 32 | 33 | client-id identifies this client. Only one connection to a gateway can 34 | be made per client-id at a time." 35 | ([wr host port client-id] 36 | (let [ecs (com.ib.client.EClientSocket. wr)] 37 | (.eConnect ecs host port client-id) 38 | ecs))) 39 | 40 | 41 | (defn disconnect 42 | "Call this function to terminate the connections with TWS. 43 | Calling this function does not cancel orders that have already been sent." 44 | [ecs] 45 | (.eDisconnect ecs)) 46 | 47 | 48 | (defn is-connected? 49 | "Call this method to check if there is a connection with TWS." 50 | [ecs] 51 | (.isConnected ecs)) 52 | 53 | 54 | (defn set-server-log-level 55 | "Call this function to set the log level used on the server. The default level 56 | is :error." 57 | [ecs log-level] 58 | (.setServerLogLevel ecs (translate :to-ib :log-level log-level))) 59 | 60 | 61 | (defn request-current-time 62 | "Returns the current system time on the server side via the currentTime() 63 | EWrapper method." 64 | [ecs] 65 | (.reqCurrentTime ecs)) 66 | 67 | 68 | (defn server-version 69 | "Returns the version of the TWS instance to which the API application is 70 | connected." 71 | [ecs] 72 | (.serverVersion ecs)) 73 | 74 | 75 | (defn connection-time 76 | "Returns the time the API application made a connection to TWS." 77 | [ecs] 78 | (translate :from-ib :connection-time (.TwsConnectionTime ecs))) 79 | 80 | 81 | ;;; 82 | ;;; Market Data 83 | ;;; 84 | (defn request-market-data 85 | "Call this function to request market data. The market data will be returned in 86 | :tick messages. 87 | 88 | For snapshots, a :tick-snapshot-end message will indicate the snapshot is done. 89 | 90 | ## Parameters 91 | - this 92 | The connection to use to make the request. Use (connect) to get this. 93 | 94 | - contract 95 | This contains attributes used to describe the contract. Use (make-contract) or 96 | (futures-contract) for example to create it. 97 | 98 | - tick-list (optional) 99 | A list of tick types: 100 | :option-volume Option Volume (currently for stocks) 101 | :option-open-interest Option Open Interest (currently for stocks) 102 | :historical-volatility Historical Volatility (currently for stocks) 103 | :option-implied-volatility Option Implied Volatility (currently for stocks) 104 | :index-future-premium Index Future Premium 105 | :miscellaneous-stats Miscellaneous Stats 106 | :mark-price Mark Price (used in TWS P&L computations) 107 | :auction-values Auction values (volume, price and imbalance) 108 | :realtime-volume RTVolume 109 | :shortable Shortable 110 | :inventory Inventory 111 | :fundamental-ratios Fundamental Ratios 112 | :realtime-historical-volatility Realtime Historical Volatility 113 | 114 | if no tick list is specified, a single snapshot of market data will come back 115 | and have the market data subscription will be immediately canceled." 116 | ([ecs ticker-id contract tick-list snapshot?] 117 | (.reqMktData ecs ticker-id 118 | (map-> com.ib.client.Contract contract) 119 | (translate :to-ib :tick-list tick-list) 120 | snapshot? nil))) 121 | 122 | 123 | (defn cancel-market-data 124 | "After calling this method, market data for the specified Id will stop 125 | flowing." 126 | [ecs ticker-id] 127 | (.cancelMktData ecs ticker-id)) 128 | 129 | 130 | (defn calculate-implied-volatility 131 | "Call this function to calculate volatility for a supplied option price and 132 | underlying price." 133 | [ecs ticker-id option-contract option-price underlying-price] 134 | (.calculateImpliedVolatility ecs ticker-id 135 | (map-> com.ib.client.Contract option-contract) 136 | option-price underlying-price)) 137 | 138 | 139 | (defn cancel-calculate-implied-volatility 140 | "Call this function to cancel a request to calculate volatility for a supplied 141 | option price and underlying price." 142 | [ecs ticker-id] 143 | (.cancelCalculateImpliedVolatility ecs ticker-id)) 144 | 145 | 146 | (defn calculate-option-price 147 | "Call this function to calculate option price and greek values for a supplied 148 | volatility and underlying price." 149 | [ecs ticker-id option-contract volatility underlying-price] 150 | (.calculateOptionPrice ecs ticker-id 151 | (map-> com.ib.client.Contract option-contract) 152 | volatility underlying-price)) 153 | 154 | 155 | (defn cancel-calculate-option-price 156 | "Call this function to cancel a request to calculate the option price and 157 | greek values for a supplied volatility and underlying price." 158 | [ecs ticker-id] 159 | (.cancelCalculateOptionPrice ecs ticker-id)) 160 | 161 | 162 | (defn request-market-data-type 163 | "The API can receive frozen market data from Trader Workstation. Frozen market 164 | data is the last data recorded in our system. During normal trading hours, the 165 | API receives real-time market data. If you use this function, you are telling 166 | TWS to automatically switch to frozen market data after the close. Then, 167 | before the opening of the next trading day, market data will automatically 168 | switch back to real-time market data. 169 | 170 | type can be :frozen or :real-time-streaming 171 | " 172 | [ecs type] 173 | (.reqMarketDataType ecs (translate :to-ib :market-data-type type))) 174 | 175 | 176 | ;;; 177 | ;;; Orders 178 | ;;; 179 | (defn place-order 180 | ([ecs order-id contract order] 181 | (.placeOrder ecs order-id 182 | (map-> com.ib.client.Contract contract) 183 | (map-> com.ib.client.Order order)))) 184 | 185 | (defn cancel-order 186 | "Call this method to cancel an order." 187 | [ecs order-id] 188 | (.cancelOrder ecs order-id)) 189 | 190 | 191 | (defn request-open-orders 192 | "Call this method to request any open orders that were placed from this API 193 | client. Each open order will be fed back through the openOrder() and 194 | orderStatus() methods on the EWrapper. 195 | 196 | Note: The client with a clientId of \"0\" will also receive the TWS-owned open 197 | orders. These orders will be associated with the client and a new orderId will 198 | be generated. This association will persist over multiple API and TWS 199 | sessions." 200 | [ecs] 201 | (.reqOpenOrders ecs)) 202 | 203 | 204 | (defn request-all-open-orders 205 | "Call this method to request all open orders that were placed from all API 206 | clients linked to one TWS, and also from the TWS. Note that you can run up to 207 | 8 API clients from a single TWS. Each open order will be fed back through the 208 | openOrder() and orderStatus() methods on the EWrapper. 209 | 210 | Note: No association is made between the returned orders and the requesting 211 | client." 212 | [ecs] 213 | (.reqAllOpenOrders ecs)) 214 | 215 | 216 | (defn request-auto-open-orders 217 | "Call this method to request that newly created TWS orders be implicitly 218 | associated with the client. When a new TWS order is created, the order will be 219 | associated with the client and automatically fed back through the openOrder() 220 | and orderStatus() methods on the EWrapper. 221 | 222 | Note: TWS orders can only be bound to clients with a clientId of 0." 223 | [ecs auto-bind?] 224 | (.reqAutoOpenOrders ecs auto-bind?)) 225 | 226 | 227 | (defn request-ids 228 | "Call this function to request the next valid ID that can be used when placing 229 | an order. After calling this method, the nextValidId() event will be 230 | triggered, and the id returned is that next valid ID. That ID will reflect any 231 | autobinding that has occurred (which generates new IDs and increments the next 232 | valid ID therein)." 233 | [ecs] 234 | (.reqIds ecs 1)) 235 | 236 | 237 | (defn exercise-options 238 | "Call this funtion to exercise options. 239 | 240 | action can be :exercise or :lapse 241 | 242 | account For institutional orders. Specifies the IB account. 243 | 244 | override? Specifies whether your setting will override the system's natural 245 | action. For example, if your action is \"exercise\" and the option is not 246 | in-the-money, by natural action the option would not exercise. If you have 247 | override? set to true the natural action would be overridden and the 248 | out-of-the money option would be exercised. 249 | 250 | Note: SMART is not an allowed exchange in exerciseOptions() calls, and TWS 251 | does a request for the position in question whenever any API initiated 252 | exercise or lapse is attempted." 253 | [ecs ticker-id contract action quantity account override?] 254 | (.exerciseOptions ecs ticker-id 255 | (map-> com.ib.client.Contract contract) 256 | (translate :to-ib :exercise-action action) 257 | quantity 258 | (if override? 1 0))) 259 | 260 | 261 | (defn request-global-cancel 262 | "Use this method to cancel all open orders globally. It cancels both API and 263 | TWS open orders. 264 | 265 | If the order was created in TWS, it also gets canceled. If the order was 266 | initiated in the API, it also gets canceled." 267 | [ecs] 268 | (.reqGlobalCancel ecs)) 269 | 270 | 271 | ;;; 272 | ;;; Account and Portfolio 273 | ;;; 274 | (defn request-account-updates 275 | "Call this function to start getting account values, portfolio, and last 276 | update time information. The account data will be fed back through the 277 | updateAccountTime(), updateAccountValue() and updatePortfolio() EWrapper 278 | methods. 279 | 280 | The account information resulting from the invocation of reqAccountUpdates 281 | is the same information that appears in Trader Workstation’s Account Window. 282 | When trying to determine the definition of each variable or key within the API 283 | account data, it is essential that you use the TWS Account Window as 284 | guidance." 285 | [ecs subscribe? account-code] 286 | (.reqAccountUpdates ecs subscribe? account-code)) 287 | 288 | 289 | (defn request-account-summary 290 | "Call this method to request and keep up to date the data that appears on the 291 | TWS Account Window Summary tab. The data is returned by accountSummary(). 292 | 293 | Note: This request can only be made when connected to a Financial Advisor (FA) 294 | account." 295 | [ecs request-id group tags] 296 | (.reqAccountSummary ecs request-id group tags)) 297 | 298 | 299 | (defn cancel-account-summary 300 | "Cancels the request for Account Window Summary tab data." 301 | [ecs request-id] 302 | (.cancelAccountSummary ecs 63 1 request-id)) 303 | 304 | 305 | (defn request-positions 306 | "Requests real-time position data for all accounts." 307 | [ecs] 308 | (.reqPositions ecs)) 309 | 310 | 311 | (defn cancel-positions 312 | "Cancels real-time position updates." 313 | [ecs] 314 | (.cancelPositions ecs)) 315 | 316 | 317 | ;;; 318 | ;;; Executions 319 | ;;; 320 | (defn request-executions 321 | "When this method is called, the execution reports from the last 24 hours that 322 | meet the filter criteria are downloaded to the client via the execDetails() 323 | method. To view executions beyond the past 24 hours, open the Trade Log in TWS 324 | and, while the Trade Log is displayed, request the executions again from the 325 | API." 326 | [ecs execution-filter] 327 | (.reqExecutions ecs (map-> com.ib.client.ExecutionFilter execution-filter))) 328 | 329 | 330 | ;;; 331 | ;;; Contract Details 332 | ;;; 333 | (defn request-contract-details 334 | "Call this function to download all details for a particular 335 | contract. The contract details will be received in a :contract-details 336 | message" 337 | ([ecs request-id contract] 338 | (.reqContractDetails ecs request-id (map-> com.ib.client.Contract contract)))) 339 | 340 | 341 | ;;; 342 | ;;; Market Depth 343 | ;;; 344 | (defn request-market-depth 345 | "Call this method to request market depth for a specific contract. 346 | The market depth will be returned by the updateMktDepth() and 347 | updateMktDepthL2() methods." 348 | [ecs ticker-id contract rows] 349 | (.reqMktDepth ecs ticker-id (map-> com.ib.client.Contract contract) rows nil)) 350 | 351 | 352 | (defn cancel-market-depth 353 | "After calling this method, market depth data for the specified Id will stop 354 | flowing." 355 | [ecs ticker-id] 356 | (.cancelMktDepth ecs ticker-id)) 357 | 358 | 359 | ;;; 360 | ;;; News Bulletins 361 | ;;; 362 | (defn request-news-bulletins 363 | "Call this function to start receiving news bulletins. Each bulletin will 364 | be sent in a :news-bulletin, :exchange-unavailable, or :exchange-available 365 | message." 366 | ([ecs all-messages?] 367 | (.reqNewsBulletins ecs all-messages?))) 368 | 369 | 370 | (defn cancel-news-bulletins 371 | "Call this function to stop receiving news bulletins." 372 | [ecs] 373 | (.cancelNewsBulletins ecs)) 374 | 375 | 376 | ;;; 377 | ;;; Financial Advisors 378 | ;;; 379 | (defn request-managed-accounts 380 | "Call this method to request the list of managed accounts. The list will be 381 | returned by the managedAccounts() method on the EWrapper. 382 | 383 | Note: This request can only be made when connected to a Financial Advisor (FA) 384 | account" 385 | [ecs] 386 | (.reqManagedAccts ecs)) 387 | 388 | 389 | (defn request-financial-advisor-data 390 | "Call this method to request FA configuration information from TWS. The data 391 | returns in an XML string via the receiveFA() method. 392 | 393 | data-type should be one of :financial-advisor-groups 394 | :financial-advisor-profile 395 | :financial-advisor-account-aliases 396 | " 397 | [ecs data-type] 398 | (.requestFA ecs (translate :to-ib :financial-advisor-data-type data-type))) 399 | 400 | 401 | (defn replace-financial-advisor-data 402 | "Call this method to replace FA data with new xml content." 403 | [ecs data-type xml] 404 | (.replaceFA ecs (translate :to-ib :financial-advisor-data-type data-type) xml)) 405 | 406 | 407 | ;;; 408 | ;;; Market Scanners 409 | ;;; 410 | (defn request-scanner-parameters 411 | "Call this method to receive an XML document that describes the valid 412 | parameters that a scanner subscription can have." 413 | [ecs] 414 | (.reqScannerParameters ecs)) 415 | 416 | 417 | (defn request-scanner-subscription 418 | "Call this method to start receiving market scanner results through the 419 | scannerData() EWrapper method." 420 | [ecs ticker-id subscription] 421 | (.reqScannerSubscription ecs 422 | (map-> com.ib.client.ScannerSubscription subscription) 423 | nil)) 424 | 425 | 426 | (defn cancel-scanner-subscription 427 | "Call this method to stop receiving market scanner results." 428 | [ecs ticker-id] 429 | (.cancelScannerSubscription ecs ticker-id)) 430 | 431 | 432 | ;;; 433 | ;;; Historical Data 434 | ;;; 435 | (defn request-historical-data 436 | "Start receiving historical price bars stretching back s back, 437 | up till for the specified contract. The messages will have :request-id of . 438 | 439 | duration-unit should be one of :second(s), :day(s), :week(s), or :year(s). 440 | 441 | bar-size-unit should be one of :second(s), :minute(s), :hour(s), or :day(s). 442 | 443 | what-to-show should be one of :trades, :midpoint, :bid, :ask, :bid-ask, 444 | :historical-volatility, :option-implied-volatility, :option-volume, 445 | or :option-open-interest." 446 | [ecs request-id contract end duration duration-unit bar-size bar-size-unit 447 | what-to-show use-regular-trading-hours?] 448 | (let [[acceptable-duration acceptable-duration-unit] 449 | (translate :to-ib :acceptable-duration [duration duration-unit])] 450 | (.reqHistoricalData ecs 451 | request-id 452 | (map-> com.ib.client.Contract contract) 453 | (translate :to-ib :date-time end) 454 | (translate :to-ib :duration [acceptable-duration 455 | acceptable-duration-unit]) 456 | (translate :to-ib :bar-size [bar-size bar-size-unit]) 457 | (translate :to-ib :what-to-show what-to-show) 458 | (if use-regular-trading-hours? 1 0) 459 | 2 nil))) 460 | 461 | 462 | (defn cancel-historical-data 463 | "Call this method to stop receiving historical data results." 464 | [ecs request-id] 465 | (.cancelHistoricalData ecs request-id)) 466 | 467 | 468 | ;;; 469 | ;;; Real Time Bars 470 | ;;; 471 | (defn request-real-time-bars 472 | "Start receiving real time bar results." 473 | [ecs request-id contract what-to-show use-regular-trading-hours?] 474 | (.reqRealTimeBars ecs request-id 475 | (map-> com.ib.client.Contract contract) 476 | 5 477 | (translate :to-ib :what-to-show what-to-show) 478 | use-regular-trading-hours?)) 479 | 480 | 481 | (defn cancel-real-time-bars 482 | "Call this function to stop receiving real time bars for the passed in request-id" 483 | [ecs request-id] 484 | (.cancelRealTimeBars ecs request-id)) 485 | 486 | 487 | ;;; 488 | ;;; Fundamental Data 489 | ;;; 490 | (defn request-fundamental-data 491 | "Call this function to receive Reuters global fundamental data. There must be a 492 | subscription to Reuters Fundamental set up in Account Management before you 493 | can receive this data." 494 | [ecs request-id contract report-type] 495 | (.reqFundamentalData ecs request-id 496 | (map-> com.ib.client.Contract contract) 497 | (translate :to-ib :report-type report-type))) 498 | 499 | 500 | (defn cancel-fundamental-data 501 | "Call this function to stop receiving Reuters global fundamental data." 502 | [ecs request-id] 503 | (.cancelFundamentalData ecs request-id)) 504 | 505 | 506 | ;;; 507 | ;;; Display Groups 508 | ;;; 509 | (defn query-display-groups 510 | [ecs request-id] 511 | (.queryDisplayGroups ecs request-id)) 512 | 513 | 514 | (defn subscribe-to-group-events 515 | "group-id The ID of the group, currently it is a number from 1 to 7." 516 | [ecs request-id group-id] 517 | (.subscribeToGroupEvents ecs request-id group-id)) 518 | 519 | 520 | (defn update-display-group 521 | "request-id The requestId specified in subscribeToGroupEvents(). 522 | 523 | contract-info The encoded value that uniquely represents the contract in IB. 524 | Possible values include: 525 | 526 | none = empty selection 527 | contractID@exchange – any non-combination contract. 528 | Examples: 8314@SMART for IBM SMART; 529 | 8314@ARCA for IBM @ARCA. 530 | combo = if any combo is selected. 531 | " 532 | [ecs request-id contract-info] 533 | (.updateDisplayGroup ecs request-id contract-info)) 534 | 535 | 536 | (defn unsubscribe-from-group-events 537 | [ecs request-id] 538 | (.unsubscribeFromGroupEvents ecs request-id)) 539 | -------------------------------------------------------------------------------- /src/ib_re_actor/translation.clj: -------------------------------------------------------------------------------- 1 | (ns ib-re-actor.translation 2 | (:require 3 | [clj-time.coerce :as tc] 4 | [clj-time.core :as time] 5 | [clj-time.format :as tf] 6 | [clojure.string :as str])) 7 | 8 | (defmulti ^:dynamic translate 9 | "Translate to or from a value from the Interactive Brokers API. 10 | 11 | Examples: 12 | user> (translate :to-ib :duration-unit :seconds) 13 | \"S\" 14 | user> (translate :from-ib :duration-unit \"S\") 15 | :second" 16 | (fn [direction table-name _] [direction table-name])) 17 | 18 | (defmulti ^:dynamic valid? 19 | "Check to see if a given value is an entry in the translation table. 20 | 21 | Examples: 22 | user> (valid? :to-ib :duration-unit :s) 23 | false 24 | user> (valid? :to-ib :duration-unit :seconds) 25 | true" 26 | (fn [direction table-name _] [direction table-name])) 27 | 28 | (defmacro translation-table 29 | "Creates a table for translating to and from string values from the Interactive 30 | Brokers API, as well as translate methods to and from the IB value and a method 31 | to check if if a given value is valid (known)." 32 | 33 | ([name to-table] 34 | `(let [to-table# ~to-table 35 | from-table# (zipmap (vals to-table#) 36 | (keys to-table#))] 37 | (translation-table ~name to-table# from-table#))) 38 | ([name to-table from-table] 39 | (let [table-name (keyword name)] 40 | `(let [to-table# ~to-table 41 | from-table# ~from-table] 42 | 43 | (def ~name to-table#) 44 | 45 | (defmethod valid? [:to-ib ~table-name] [_# _# val#] 46 | (contains? to-table# val#)) 47 | 48 | (defmethod translate [:to-ib ~table-name] [_# _# val#] 49 | (when val# 50 | (cond 51 | (valid? :to-ib ~table-name val#) 52 | (to-table# val#) 53 | 54 | (string? val#) 55 | val# 56 | 57 | :else 58 | (throw (ex-info (str "Can't translate to IB " ~table-name " " val#) 59 | {:value val# 60 | :table ~table-name 61 | :valid-values (keys to-table#)}))))) 62 | 63 | (defmethod valid? [:from-ib ~table-name] [_# _# val#] 64 | (contains? from-table# val#)) 65 | 66 | (defmethod translate [:from-ib ~(keyword name)] [_# _# val#] 67 | (when val# 68 | (cond 69 | (valid? :from-ib ~table-name val#) 70 | (from-table# val#) 71 | 72 | (string? val#) 73 | val# 74 | 75 | :else 76 | (throw (ex-info (str "Can't translate from IB " ~table-name " " val#) 77 | {:value val# 78 | :table ~table-name 79 | :valid-values (vals to-table#)}))))))))) 80 | 81 | (translation-table duration-unit 82 | {:second "S" 83 | :seconds "S" 84 | :day "D" 85 | :days "D" 86 | :week "W" 87 | :weeks "W" 88 | :month "M" 89 | :months "M" 90 | :year "Y" 91 | :years "Y"}) 92 | 93 | (defmethod translate [:to-ib :acceptable-duration] [_ _ [val unit]] 94 | (case unit 95 | :second [val :second] 96 | :seconds [val :seconds] 97 | :minute [(* 60 val) :seconds] 98 | :minutes [(* 60 val) :seconds] 99 | :hour [(* 60 60 val) :seconds] 100 | :hours [(* 60 60 val) :seconds] 101 | :day [val :day] 102 | :days [val :days] 103 | :week [val :week] 104 | :weeks [val :weeks] 105 | :month [val :month] 106 | :months [val :months] 107 | :year [val :year] 108 | :years [val :years])) 109 | 110 | (translation-table security-type 111 | {:equity "STK" 112 | :option "OPT" 113 | :future "FUT" 114 | :index "IND" 115 | :future-option "FOP" 116 | :cash "CASH" 117 | :bag "BAG" 118 | :warrant "WAR" 119 | :dutch-warrant "IOPT" 120 | :cfd "CFD" 121 | :commodity "CMDTY"}) 122 | 123 | (defmethod translate [:to-ib :bar-size-unit] [_ _ unit] 124 | (case unit 125 | :second "secs" 126 | :seconds "secs" 127 | :minute "min" 128 | :minutes "mins" 129 | :hour "hour" 130 | :hours "hours" 131 | :day "day" 132 | :days "days" 133 | :week "W" 134 | :month "M" 135 | )) 136 | 137 | (defmethod translate [:from-ib :bar-size-unit] [_ _ unit] 138 | (case unit 139 | "sec" :second 140 | "secs" :seconds 141 | "min" :minute 142 | "mins" :minutes 143 | "hour" :hour 144 | "hours" :hours 145 | "day" :day 146 | "days" :days)) 147 | 148 | (translation-table what-to-show 149 | {:trades "TRADES" 150 | :midpoint "MIDPOINT" 151 | :bid "BID" 152 | :ask "ASK" 153 | :bid-ask "BID_ASK" 154 | :historical-volatility "HISTORICAL_VOLATILITY" 155 | :option-implied-volatility "OPTION_IMPLIED_VOLATILITY" 156 | :option-volume "OPTION_VOLUME" 157 | :option-open-interest "OPTION_OPEN_INTEREST"}) 158 | 159 | (translation-table time-in-force 160 | {:day "DAY" 161 | :good-to-close "GTC" 162 | :immediate-or-cancel "IOC" 163 | :good-till-date "GTD"}) 164 | 165 | (translation-table order-action 166 | {:buy "BUY" 167 | :sell "SELL" 168 | :sell-short "SSHORT"}) 169 | 170 | (translation-table order-type 171 | {:ACTIVETIM "ACTIVETIM" 172 | :ADJUST "ADJUST" 173 | :ALERT "ALERT" 174 | :ALGO "ALGO" 175 | :ALGOLTH "ALGOLTH" 176 | :ALLOC "ALLOC" 177 | :AON "AON" 178 | :AUC "AUC" 179 | :average-cost "AVGCOST" 180 | :basket "BASKET" 181 | :BOARDLOT "BOARDLOT" 182 | :box-top "BOXTOP" 183 | :COND "COND" 184 | :CONDORDER "CONDORDER" 185 | :CONSCOST "CONSCOST" 186 | :DARKPOLL "DARKPOLL" 187 | :DAY "DAY" 188 | :DEACT "DEACT" 189 | :DEACTDIS "DEACTDIS" 190 | :DEACTEOD "DEACTEOD" 191 | :DIS "DIS" 192 | :EVRULE "EVRULE" 193 | :FOK "FOK" 194 | :good-after-time "GAT" 195 | :good-till-date "GTD" 196 | :good-till-canceled "GTC" 197 | :GTT "GTT" 198 | :HID "HID" 199 | :ICE "ICE" 200 | :IMB "IMB" 201 | :immediate-or-cancel "IOC" 202 | :limit "LMT" 203 | :limit-close "LMTCLS" 204 | :limit-on-close "LOC" 205 | :limit-on-open "LOO" 206 | :limit-if-touched "LIT" 207 | :LTH "LTH" 208 | :market "MKT" 209 | :market-close "MKTCLS" 210 | :market-on-close "MOC" 211 | :market-to-limit "MTL" 212 | :market-with-protection "MKTPRT" 213 | :market-if-touched "MIT" 214 | :market-on-open "MOO" 215 | :NONALGO "NONALGO" 216 | :one-cancels-all "OCA" 217 | :OPG "OPG" 218 | :OPGREROUT "OPGREROUT" 219 | :pegged-to-market "PEGMKT" 220 | :pegged-to-midpoint "PEGMID" 221 | :POSTONLY "POSTONLY" 222 | :PREOPGRTH "PREOPGRTH" 223 | :relative "REL" 224 | :request-for-quote "QUOTE" 225 | :RTH "RTH" 226 | :RTHIGNOPG "RTHIGNOPG" 227 | :scale "SCALE" 228 | :SCALERST "SCALERST" 229 | :stop "STP" 230 | :stop-limit "STPLMT" 231 | :SWEEP "SWEEP" 232 | :TIMEPRIO "TIMEPRIO" 233 | :trail "TRAIL" 234 | :trail-limit "TRAILLIMIT" 235 | :trailing-limit-if-touched "TRAILLIT" 236 | :trailing-market-if-touched "TRAILMIT" 237 | :trailing-stop "TRAIL" 238 | :trailing-stop-limit "TRAILLMT" 239 | :VWAP "VWAP" 240 | :volatility "VOL" 241 | :what-if "WHATIF"}) 242 | 243 | (translation-table order-status 244 | { 245 | :pending-submit "PendingSubmit" 246 | :pending-cancel "PendingCancel" 247 | :pre-submitted "PreSubmitted" 248 | :submitted "Submitted" 249 | :cancelled "Cancelled" 250 | :filled "Filled" 251 | :inactive "Inactive" 252 | }) 253 | 254 | (translation-table security-id-type 255 | {:isin "ISIN" 256 | :cusip "CUSIP" 257 | :sedol "SEDOL" 258 | :ric "RIC"}) 259 | 260 | (translation-table tick-field-code 261 | {:bid-size 0 262 | :bid-price 1 263 | :ask-price 2 264 | :ask-size 3 265 | :last-price 4 266 | :last-size 5 267 | :high 6 268 | :low 7 269 | :volume 8 270 | :close 9 271 | :bid-option-computation 10 272 | :ask-option-computation 11 273 | :last-option-computation 12 274 | :model-option-computation 13 275 | :open 14 276 | :low-13-week 15 277 | :high-13-week 16 278 | :low-26-week 17 279 | :high-26-week 18 280 | :low-52-week 19 281 | :high-52-week 20 282 | :avg-volume 21 283 | :open-interest 22 284 | :option-historical-volatility 23 285 | :option-implied-volatility 24 286 | :option-bid-exchange 25 287 | :option-ask-exchange 26 288 | :option-call-open-interest 27 289 | :option-put-open-interest 28 290 | :option-call-volume 29 291 | :option-put-volume 30 292 | :index-future-premium 31 293 | :bid-exchange 32 294 | :ask-exchange 33 295 | :auction-volume 34 296 | :auction-price 35 297 | :auction-imbalance 36 298 | :mark-price 37 299 | :bid-efp-computation 38 300 | :ask-efp-computation 39 301 | :last-efp-computation 40 302 | :open-efp-computation 41 303 | :high-efp-computation 42 304 | :low-efp-computation 43 305 | :close-efp-computation 44 306 | :last-timestamp 45 307 | :shortable 46 308 | :fundamental-ratios 47 309 | :realtime-volume 48 310 | :halted 49 311 | :bid-yield 50 312 | :ask-yield 51 313 | :last-yield 52 314 | :cust-option-computation 53 315 | :trade-count 54 316 | :trade-rate 55 317 | :volume-rate 56} 318 | ) 319 | 320 | (translation-table generic-tick-type 321 | { 322 | :option-volume 100 ; :option-call-volume, :option-put-volume 323 | :option-open-interest 101 ; :option-call-open-interest, :option-put-open-interest 324 | :historical-volatility 104 ; :option-historical-volatility 325 | :option-implied-volatility 106 ; :option-implied-volatility 326 | :index-future-premium 162 ; :index-future-premium 327 | :miscellaneous-stats 165 ; :low-13-week, :high-13-week, :low-26-week, :high-26-week, :low-52-week, :high-52-week, :avg-volume 21 328 | :mark-price 221 ; :mark-price 329 | :auction-values 225 ; :auction-volume, :auction-price, :auction-imbalance 330 | :realtime-volume 233 ; :realtime-volume 331 | :shortable 236 ; :shortable 332 | :inventory 256 ; 333 | :fundamental-ratios 258 ; :fundamental-ratios 334 | :realtime-historical-volatility 411 ; 58? 335 | }) 336 | 337 | (translation-table log-level 338 | {:system 1 339 | :error 2 340 | :warning 3 341 | :informational 4 342 | :detail 5}) 343 | 344 | (defmethod translate [:to-ib :tick-list] [_ _ val] 345 | (->> val 346 | (map #(cond 347 | (valid? :to-ib :tick-field-code %) 348 | (translate :to-ib :tick-field-code %) 349 | 350 | (valid? :to-ib :generic-tick-type %) 351 | (translate :to-ib :generic-tick-type %) 352 | 353 | :else %)) 354 | (map str) 355 | (clojure.string/join ","))) 356 | 357 | (translation-table fundamental-ratio 358 | { 359 | :closing-price "NPRICE" 360 | :3-year-ttm-growth "Three_Year_TTM_Growth" 361 | :ttm-over-ttm "TTM_over_TTM" 362 | :12-month-high "NHIG" 363 | :12-month-low "NLOW" 364 | :pricing-date "PDATE" 365 | :10-day-average-volume "VOL10DAVG" 366 | :market-cap "MKTCAP" 367 | :eps-exclusing-extraordinary-items "TTMEPSXCLX" 368 | :eps-normalized "AEPSNORM" 369 | :revenue-per-share "TTMREVPS" 370 | :common-equity-book-value-per-share "QBVPS" 371 | :tangible-book-value-per-share "QTANBVPS" 372 | :cash-per-share "QCSHPS" 373 | :cash-flow-per-share "TTMCFSHR" 374 | :dividends-per-share "TTMDIVSHR" 375 | :dividend-rate "IAD" 376 | :pe-excluding-extraordinary-items "PEEXCLXOR" 377 | :pe-normalized "APENORM" 378 | :price-to-sales "TMPR2REV" 379 | :price-to-tangible-book "PR2TANBK" 380 | :price-to-cash-flow-per-share "TTMPRCFPS" 381 | :price-to-book "PRICE2BK" 382 | :current-ration "QCURRATIO" 383 | :quick-ratio "QQUICKRATI" 384 | :long-term-debt-to-equity "QLTD2EQ" 385 | :total-debt-to-equity "QTOTD2EQ" 386 | :payout-ratio "TTMPAYRAT" 387 | :revenue "TTMREV" 388 | :ebita "TTMEBITD" 389 | :ebt "TTMEBT" 390 | :niac "TTMNIAC" 391 | :ebt-normalized "AEBTNORM" 392 | :niac-normalized "ANIACNORM" 393 | :gross-margin "TTMGROSMGN" 394 | :net-profit-margin "TTMNPMGN" 395 | :operating-margin "TTMOPMGN" 396 | :pretax-margin "APTMGNPCT" 397 | :return-on-average-assets "TTMROAPCT" 398 | :return-on-average-equity "TTMROEPCT" 399 | :roi "TTMROIPCT" 400 | :revenue-change "REVCHNGYR" 401 | :revenue-change-ttm "TTMREVCHG" 402 | :revenue-growth "REVTRENDGR" 403 | :eps-change "EPSCHNGYR" 404 | :eps-change-ttm "TTMEPSCHG" 405 | :eps-growth "EPSTRENDGR" 406 | :dividend-growth "DIVGRPCT"}) 407 | 408 | (translation-table account-value-key 409 | { 410 | :account-code "AccountCode" 411 | :account-ready "AccountReady" 412 | :account-type "AccountType" 413 | :accrued-cash "AccruedCash" 414 | :accrued-cash-commodities "AccruedCash-C" 415 | :accrued-cash-stock "AccruedCash-S" 416 | :accrued-dividend "AccruedDividend" 417 | :accrued-dividend-commodities "AccruedDividend-C" 418 | :accrued-dividend-stock "AccruedDividend-S" 419 | :available-funds "AvailableFunds" 420 | :available-funds-commodities "AvailableFunds-C" 421 | :available-funds-stock "AvailableFunds-S" 422 | :billable "Billable" 423 | :billable-commodities "Billable-C" 424 | :billable-stock "Billable-S" 425 | :buying-power "BuyingPower" 426 | :cash-balance "CashBalance" 427 | :corporate-bond-value "CorporateBondValue" 428 | :currency "Currency" 429 | :cushion "Cushion" 430 | :day-trades-remaining "DayTradesRemaining" 431 | :day-trades-remaining-T+1 "DayTradesRemainingT+1" 432 | :day-trades-remaining-T+2 "DayTradesRemainingT+2" 433 | :day-trades-remaining-T+3 "DayTradesRemainingT+3" 434 | :day-trades-remaining-T+4 "DayTradesRemainingT+4" 435 | :equity-with-loan-value "EquityWithLoanValue" 436 | :equity-with-loan-value-commodities "EquityWithLoanValue-C" 437 | :equity-with-loan-value-stock "EquityWithLoanValue-S" 438 | :excess-liquidity "ExcessLiquidity" 439 | :excess-liquidity-commodities "ExcessLiquidity-C" 440 | :excess-liquidity-stock "ExcessLiquidity-S" 441 | :exchange-rate "ExchangeRate" 442 | :full-available-funds "FullAvailableFunds" 443 | :full-available-funds-commodities "FullAvailableFunds-C" 444 | :full-available-funds-stock "FullAvailableFunds-S" 445 | :full-excess-liquidity "FullExcessLiquidity" 446 | :full-excess-liquidity-commodities "FullExcessLiquidity-C" 447 | :full-excess-liquidity-stock "FullExcessLiquidity-S" 448 | :full-initial-margin-requirement "FullInitMarginReq" 449 | :full-initial-margin-requirement-commodities "FullInitMarginReq-C" 450 | :full-initial-margin-requirement-stock "FullInitMarginReq-S" 451 | :full-maintenance-margin-requirement "FullMaintMarginReq" 452 | :full-maintenance-margin-requirement-commodities "FullMaintMarginReq-C" 453 | :full-maintenance-margin-requirement-stock "FullMaintMarginReq-S" 454 | :fund-value "FundValue" 455 | :future-option-value "FutureOptionValue" 456 | :futures-profit-loss "FuturesPNL" 457 | :fx-cash-balance "FxCashBalance" 458 | :gross-position-value "GrossPositionValue" 459 | :net-liquidation "NetLiquidation" 460 | :gross-position-value-stock "GrossPositionValue-S" 461 | :indian-stock-haircut "IndianStockHaircut" 462 | :indian-stock-haircut-commodities "IndianStockHaircut-C" 463 | :indian-stock-haircut-stock "IndianStockHaircut-S" 464 | :initial-margin-requirement "InitMarginReq" 465 | :initial-margin-requirement-commodities "InitMarginReq-C" 466 | :initial-margin-requirement-stock "InitMarginReq-S" 467 | :leverage-stock "Leverage-S" 468 | :look-ahead-available-funds "LookAheadAvailableFunds" 469 | :look-ahead-available-funds-commodities "LookAheadAvailableFunds-C" 470 | :look-ahead-available-funds-stock "LookAheadAvailableFunds-S" 471 | :look-ahead-excess-liquidity "LookAheadExcessLiquidity" 472 | :look-ahead-excess-liquidity-commodities "LookAheadExcessLiquidity-C" 473 | :look-ahead-excess-liquidity-stock "LookAheadExcessLiquidity-S" 474 | :look-ahead-initial-margin-requirement "LookAheadInitMarginReq" 475 | :look-ahead-initial-margin-requirement-commodities "LookAheadInitMarginReq-C" 476 | :look-ahead-initial-margin-requirement-stock "LookAheadInitMarginReq-S" 477 | :look-ahead-maintenance-margin-requirement "LookAheadMaintMarginReq" 478 | :look-ahead-maintenance-margin-requirement-commodities "LookAheadMaintMarginReq-C" 479 | :look-ahead-maintenance-margin-requirement-stock "LookAheadMaintMarginReq-S" 480 | :look-ahead-next-change "LookAheadNextChange" 481 | :maintenance-margin-requirement "MaintMarginReq" 482 | :maintenance-margin-requirement-commodities "MaintMarginReq-C" 483 | :maintenance-margin-requirement-stock "MaintMarginReq-S" 484 | :money-market-fund-value "MoneyMarketFundValue" 485 | :mutual-fund-value "MutualFundValue" 486 | :net-dividend "NetDividend" 487 | :net-liquidation-commodities "NetLiquidation-C" 488 | :net-liquidation-stock "NetLiquidation-S" 489 | :net-liquidation-by-currency "NetLiquidationByCurrency" 490 | :option-market-value "OptionMarketValue" 491 | :pa-shares-value "PASharesValue" 492 | :pa-shares-value-commodities "PASharesValue-C" 493 | :pa-shares-value-stock "PASharesValue-S" 494 | :post-expiration-excess "PostExpirationExcess" 495 | :post-expiration-excess-commodities "PostExpirationExcess-C" 496 | :post-expiration-excess-stock "PostExpirationExcess-S" 497 | :post-expiration-margin "PostExpirationMargin" 498 | :post-expiration-margin-commodities "PostExpirationMargin-C" 499 | :post-expiration-margin-stock "PostExpirationMargin-S" 500 | :profit-loss "PNL" 501 | :previous-day-equity-with-loan-value "PreviousDayEquityWithLoanValue" 502 | :previous-day-equity-with-loan-value-stock "PreviousDayEquityWithLoanValue-S" 503 | :realized-profit-loss "RealizedPnL" 504 | :regulation-T-equity "RegTEquity" 505 | :regulation-T-equity-stock "RegTEquity-S" 506 | :regulation-T-margin "RegTMargin" 507 | :regulation-T-margin-stock "RegTMargin-S" 508 | :sma "SMA" 509 | :sma-stock "SMA-S" 510 | :stock-market-value "StockMarketValue" 511 | :t-bill-value "TBillValue" 512 | :t-bond-value "TBondValue" 513 | :total-cash-balance "TotalCashBalance" 514 | :total-cash-value "TotalCashValue" 515 | :total-cash-value-commodities "TotalCashValue-C" 516 | :total-cash-value-stock "TotalCashValue-S" 517 | :trading-type-stock "TradingType-S" 518 | :unaltered-initial-margin-requirement "UnalteredInitMarginReq" 519 | :unaltered-maintenance-margin-requirement "UnalteredMaintMarginReq" 520 | :unrealized-profit-loss "UnrealizedPnL" 521 | :warrants-value "WarrantValue" 522 | :what-if-portfolio-margin-enabled "WhatIfPMEnabled" 523 | }) 524 | 525 | (defn numeric-account-value? [key] 526 | (contains? #{:accrued-cash :accrued-cash-commodities :accrued-cash-stock 527 | :accrued-dividend :accrued-dividend-commodities :accrued-dividend-stock 528 | :available-funds :available-funds-commodities :available-funds-stock 529 | :billable :billable-commodities :billable-stock 530 | :buying-power :cash-balance :corporate-bond-value :cushion 531 | :equity-with-loan-value :equity-with-loan-value-commodities :equity-with-loan-value-stock 532 | :excess-liquidity :excess-liquidity-commodities :excess-liquidity-stock 533 | :exchange-rate 534 | :full-available-funds :full-available-funds-commodities :full-available-funds-stock 535 | :full-excess-liquidity :full-excess-liquidity-commodities :full-excess-liquidity-stock 536 | :full-initial-margin-requirement :full-initial-margin-requirement-commodities :full-initial-margin-requirement-stock 537 | :full-maintenance-margin-requirement :full-maintenance-margin-requirement-commodities :full-maintenance-margin-requirement-stock 538 | :fund-value :future-option-value :futures-profit-loss :fx-cash-balance 539 | :gross-position-value :gross-position-values-commodities :gross-position-value-stock 540 | :indian-stock-haricut :indian-stock-haircut-commodities :indian-stock-haircut-stock 541 | :initial-margin-requirement :initial-margin-requirement-commodities :initial-margin-requirement-stock 542 | :leverage :leverage-commodities :leverage-stock 543 | :look-ahead-available-funds :look-ahead-available-funds-commodities :look-ahead-available-funds-stock 544 | :look-ahead-excess-liquidity :look-ahead-excess-liquidity-commodities :look-ahead-excess-liquidity-stock 545 | :look-ahead-initial-margin-requirement :look-ahead-initial-margin-requirement-commodities :look-ahead-initial-margin-requirement-stock 546 | :look-ahead-maintenance-margin-requirement :look-ahead-maintenance-margin-requirement-commodities 547 | :look-ahead-maintenance-margin-requirement-stock 548 | :look-ahead-next-change 549 | :maintenance-margin-requirement :maintenance-margin-requirement-commodities :maintenance-margin-requirement-stock 550 | :money-market-fund-value :mutual-fund-value :net-dividend 551 | :net-liquidation :net-liquidation-commodities :net-liquidation-stock 552 | :net-liquidation-by-currency :option-market-value 553 | :pa-shares-value :pa-shares-value-commodities :pa-shares-value-stock 554 | :post-expiration-margin 555 | :post-expiration-margin-commodities :post-expiration-margin-stock 556 | :post-expiration-excess 557 | :post-expiration-excess-commodities :post-expiration-excess-stock 558 | :previous-day-equity-with-loan-value :previous-day-equity-with-loan-value-commodities :previous-day-equity-with-loan-value-stock 559 | :realized-profit-loss 560 | :regulation-T-equity :regulation-T-equity-commodities :regulation-T-equity-stock 561 | :regulation-T-margin :regulation-T-margin-commodities :regulation-T-margin-stock 562 | :sma :sma-commodities :sma-stock 563 | :stock-market-value :t-bill-value :t-bond-value :total-cash-balance 564 | :total-cash-value :total-cash-value-commodities :total-cash-value-stock 565 | :unaltered-initial-margin-requirement :unaltered-maintenance-margin-requirement 566 | :unrealized-profit-loss :warrants-value 567 | } key)) 568 | 569 | (defn integer-account-value? [key] 570 | (contains? #{:day-trades-remaining :day-trades-remaining-T+1 :day-trades-remaining-T+2 571 | :day-trades-remaining-T+3 :day-trades-remaining-T+4 572 | } key)) 573 | 574 | (defn boolean-account-value? [key] 575 | (contains? #{:account-ready :profit-loss :what-if-portfolio-margin-enabled} key)) 576 | 577 | (translation-table market-depth-row-operation 578 | { 579 | :insert 0 580 | :update 1 581 | :delete 2 582 | }) 583 | 584 | (translation-table market-depth-side 585 | { 586 | :ask 0 587 | :bid 1 588 | }) 589 | 590 | (translation-table report-type 591 | {:company-overview "ReportSnapshot" 592 | :financial-summary "ReportsFinSummary" 593 | :financial-ratios "ReportRatios" 594 | :financial-statements "ReportsFinStatements" 595 | :analyst-estimates "RESC" 596 | :company-calendar "CalendarReport" 597 | }) 598 | 599 | (translation-table rule-80A 600 | {:individual "I" 601 | :agency "A" 602 | :agent-other-member "W" 603 | :individual-PTIA "J" 604 | :agency-PTIA "U" 605 | :agent-other-member-PTIA "M" 606 | :individual-PT "K" 607 | :agency-PT "Y" 608 | :agent-other-member-PT "N"}) 609 | 610 | (translation-table market-data-type 611 | {:real-time-streaming 1 612 | :frozen 2}) 613 | 614 | (translation-table boolean-int 615 | {true 1 616 | false 0}) 617 | 618 | (translation-table execution-side 619 | {:buy "BOT" 620 | :sell "SLD"}) 621 | 622 | (translation-table financial-advisor-data-type 623 | {:financial-advisor-groups 1 624 | :financial-advisor-profile 2 625 | :financial-advisor-account-aliases 3}) 626 | 627 | (translation-table right 628 | {:put "PUT", 629 | :call "CALL", 630 | :none "0", 631 | :unknown "?"} 632 | {"PUT" :put 633 | "P" :put 634 | "CALL" :call 635 | "C" :call 636 | "0" :none 637 | "?" :unknown}) 638 | 639 | (defmethod translate [:to-ib :duration] [_ _ [val unit]] 640 | (str val " " (translate :to-ib :duration-unit unit))) 641 | 642 | (defmethod translate [:from-ib :duration] [_ _ val] 643 | (when val 644 | (let [[amount unit] (.split val " ")] 645 | (vector (Integer/parseInt amount) 646 | (translate :from-ib :duration-unit unit))))) 647 | 648 | (defmethod translate [:from-ib :date-time] [_ _ val] 649 | (condp instance? val 650 | java.util.Date (tc/from-date val) 651 | String (translate :from-ib :date-time (Long/parseLong val)) 652 | Long (tc/from-long (* 1000 val)))) 653 | 654 | (defmethod translate [:to-ib :date-time] [_ _ value] 655 | (when val 656 | (-> (tf/formatter "yyyyMMdd HH:mm:ss") 657 | (tf/unparse value) 658 | (str " UTC")))) 659 | 660 | (defmethod translate [:to-ib :timestamp] [_ _ val] 661 | (condp instance? val 662 | java.util.Date (tc/from-date val) 663 | org.joda.time.DateTime (translate :to-ib :timestamp 664 | (-> (tf/formatter "yyyyMMdd-HH:mm:ss") 665 | (tf/unparse val) 666 | (str " UTC"))) 667 | String val)) 668 | 669 | (defmethod translate [:from-ib :timestamp] [_ _ val] 670 | 671 | (cond 672 | (nil? val) nil 673 | 674 | (= (.length val) 8) 675 | (tf/parse (tf/formatter "yyyyMMdd") val) 676 | 677 | (every? #(Character/isDigit %) val) 678 | (tc/from-long (* (Long/parseLong val) 1000)) 679 | 680 | (= (.length val) 17) 681 | (tf/parse (tf/formatter "yyyyMMdd-HH:mm:ss") val) 682 | 683 | :else val)) 684 | 685 | (defmethod translate [:from-ib :time-zone] [_ _ val] 686 | (case val 687 | "GMT" "+0000" 688 | "EST" "-0500" 689 | "MST" "-0700" 690 | "PST" "-0800" 691 | "AST" "-0400" 692 | "JST" "+0900" 693 | "AET" "+1000")) 694 | 695 | (defmethod translate [:from-ib :connection-time] [_ _ val] 696 | (when val 697 | (let [tokens (vec (.split val " ")) 698 | timezone-token (get tokens 2)] 699 | (when timezone-token 700 | (let [timezone-offset (translate :from-ib :time-zone timezone-token) 701 | tokens-with-adjusted-timezone (concat (take 2 tokens) [timezone-offset]) 702 | adjusted-date-time-string (clojure.string/join " " tokens-with-adjusted-timezone)] 703 | (tf/parse (tf/formatter "yyyyMMdd HH:mm:ss Z") 704 | adjusted-date-time-string)))))) 705 | 706 | (defmethod translate [:to-ib :connection-time] [_ _ val] 707 | (when val 708 | (tf/unparse (tf/formatter "yyyyMMdd HH:mm:ss z") val))) 709 | 710 | (defmethod translate [:to-ib :date] [_ _ val] 711 | (tf/unparse (tf/formatter "MM/dd/yyyy") val)) 712 | 713 | (defmethod translate [:from-ib :date] [_ _ val] 714 | (when val 715 | (try 716 | (tf/parse (tf/formatter "MM/dd/yyyy") val) 717 | (catch Exception e 718 | (throw (ex-info "Failed to translate from IB date value." 719 | {:value val 720 | :expected-form "MM/dd/yyyy"})))))) 721 | 722 | ;;; FIXME: We should turn time of day into some kind of data structure that does 723 | ;;; no have a date component. 724 | (defmethod translate [:from-ib :time-of-day] [_ _ val] 725 | (when val 726 | (try 727 | (tf/parse (tf/formatter "HH:mm") val) 728 | (catch Exception e 729 | (throw (ex-info "Failed to translate from IB time-of-day value." 730 | {:value val 731 | :expected-form "HH:mm"})))))) 732 | 733 | (defmethod translate [:to-ib :time-of-day] [_ _ val] 734 | (when val 735 | (try 736 | (tf/unparse (tf/formatter "HH:mm") val) 737 | (catch Exception e 738 | (throw (ex-info "Failed to translate from IB time-of-day value." 739 | {:value val 740 | :expected-form "HH:mm"})))))) 741 | 742 | 743 | (defmulti expiry-to-ib class) 744 | 745 | (defmethod expiry-to-ib org.joda.time.DateTime [time] 746 | (tf/unparse (tf/formatter "yyyyMMdd") time)) 747 | 748 | 749 | (defmethod expiry-to-ib org.joda.time.LocalDate [date] 750 | (tf/unparse-local (tf/formatter-local "yyyyMMdd") date)) 751 | 752 | 753 | (defmethod expiry-to-ib org.joda.time.YearMonth [ym] 754 | (tf/unparse-local (tf/formatter-local "yyyyMM") ym)) 755 | 756 | 757 | (defmethod translate [:to-ib :expiry] [_ _ val] 758 | (when val 759 | (expiry-to-ib val))) 760 | 761 | (defmethod translate [:from-ib :expiry] [_ _ val] 762 | (if (= val "NOEXP") nil 763 | (condp = (.length val) 764 | 6 (org.joda.time.YearMonth. 765 | (tf/parse-local-date (tf/formatter "yyyyMM") val)) 766 | 8 (tf/parse-local-date (tf/formatter "yyyyMMdd") val)))) 767 | 768 | (defmethod translate [:to-ib :bar-size] [_ _ [val unit]] 769 | (str val " " (translate :to-ib :bar-size-unit unit))) 770 | 771 | (defmethod translate [:to-ib :double-string] [_ _ val] 772 | (str val)) 773 | 774 | (defmethod translate [:from-ib :double-string] [_ _ val] 775 | (Double/parseDouble val)) 776 | 777 | (defmethod translate [:from-ib :order-types] [_ _ val] 778 | (->> (.split val ",") 779 | (map (partial translate :from-ib :order-type)))) 780 | 781 | (defmethod translate [:to-ib :order-types] [_ _ val] 782 | (str/join "," (map (partial translate :to-ib :order-type) val))) 783 | 784 | (defmethod translate [:from-ib :exchanges] [_ _ val] 785 | (str/split val #",")) 786 | 787 | (defmethod translate [:to-ib :exchanges] [_ _ val] 788 | (str/join "," val)) 789 | 790 | (defmethod translate [:from-ib :yield-redemption-date] [_ _ val] 791 | (let [year (int (Math/floor (/ val 10000))) 792 | month (int (Math/floor (/ (mod val 10000) 100))) 793 | day (int (Math/floor (mod 19720427 100)))] 794 | (time/date-time year month day))) 795 | 796 | ;; ----- 797 | ;; ## Deals with the trading hours reporting. This is really ugly. 798 | ;; IB uses their timezone definitions incorrectly. Correct them here. No, no, 799 | ;; they really do. 800 | (def ib-timezone-map 801 | {"EST" "America/New_York" 802 | "CST" "America/Chicago" 803 | "CTT" "America/Chicago" 804 | "JST" "Asia/Tokyo" 805 | "PST" "America/Los_Angeles" 806 | "IST" "Asia/Kolkata"}) 807 | 808 | (defn- to-utc 809 | "Returns a full date-time in UTC, referring to a particular time at a 810 | particular place. Place must be a TZ string such as America/Chicago. Date will 811 | only use the year-month-day fields, the min and second come from the parms." 812 | ([place date-time] 813 | (to-utc place date-time 814 | (time/hour date-time) (time/minute date-time) (time/second date-time))) 815 | ([place date hour minute] 816 | (to-utc place date hour minute 0)) 817 | ([place date hour minute second] 818 | (let [zone (time/time-zone-for-id (or (ib-timezone-map place) place))] 819 | (time/to-time-zone 820 | (time/from-time-zone 821 | (time/date-time (time/year date) (time/month date) (time/day date) hour minute second) 822 | zone) 823 | (time/time-zone-for-id "UTC"))))) 824 | 825 | (defn- th-days 826 | "Returns a seq of the days in an interval" 827 | [s] 828 | (str/split s #";")) 829 | 830 | (defn- th-components [s] 831 | (str/split s #":")) 832 | 833 | ;; NB: Closed days are represented as 0 length intervals on that day. 834 | (defn- th-intervals [[day s]] 835 | (if (= s "CLOSED") 836 | [[(str day "0000") (str day "0000")]] 837 | (map #(mapv (partial str day) (str/split % #"-")) (str/split s #",")))) 838 | 839 | ;; Convert to Joda intervals 840 | (defn- joda-interval [tz [start end]] 841 | (let [start-dt (to-utc tz (tf/parse (tf/formatter "yyyyMMddHHmm") start)) 842 | end-dt (to-utc tz (tf/parse (tf/formatter "yyyyMMddHHmm") end)) 843 | mod-start (if (time/after? start-dt end-dt) 844 | (time/minus start-dt (time/days 1)) 845 | start-dt)] 846 | (time/interval mod-start end-dt))) 847 | 848 | ;;added changed ->> to some->> which apparently is necessary for this to work with 849 | ;;include-expired? contracts, as trading-hours seems to be nil/empty for that 850 | (defmethod translate [:from-ib :trading-hours] [_ _ [tz t-string]] 851 | (some->> t-string 852 | th-days 853 | (map th-components) 854 | (mapcat th-intervals) 855 | (map (partial joda-interval tz)))) 856 | 857 | (defmethod translate [:from-ib :security-id-list] [_ _ tag-values] 858 | (when tag-values 859 | (into {} 860 | (for [tag-value tag-values] 861 | [(translate :from-ib :security-id-type (.m_tag tag-value)) 862 | (.m_value tag-value)])))) 863 | --------------------------------------------------------------------------------