}
254 |
255 | The quote parser does not have to return a map; it can actually return
256 | any type you like. Function `parse-quote-item` is used to parse one
257 | field of the raw quote; it knows the data type (string, double, int,
258 | etc.) of every field and will return the correct value and type.
259 | Function `parse-last-trade-date-time` combines the values of
260 | `:LastTradeDate` and `:LastTradeTime` and returns them as a
261 | `org.joda.time.DateTime` object in UTC. You will want to use this
262 | function if you need access to the last trade time and date because
263 | the data returned by Yahoo! Finance for these fields is a bit
264 | inconsistent; the date value is in UTC, but the time value is in the
265 | time zone of the North American East Coast! Function
266 | `parse-last-trade-date-time` corrects for this and returns a date time
267 | in UTC.
268 |
269 | ## Digging Deeper
270 |
271 | Stockings includes even more data and functions to help you dig into it.
272 | Please, consult the detailed API documentation below to find out more."})
273 |
274 |
--------------------------------------------------------------------------------
/LICENSE.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Eclipse Public License - Version 1.0
8 |
25 |
26 |
27 |
28 |
29 |
30 | Eclipse Public License - v 1.0
31 |
32 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
35 | AGREEMENT.
36 |
37 | 1. DEFINITIONS
38 |
39 | "Contribution" means:
40 |
41 | a) in the case of the initial Contributor, the initial
42 | code and documentation distributed under this Agreement, and
43 | b) in the case of each subsequent Contributor:
44 | i) changes to the Program, and
45 | ii) additions to the Program;
46 | where such changes and/or additions to the Program
47 | originate from and are distributed by that particular Contributor. A
48 | Contribution 'originates' from a Contributor if it was added to the
49 | Program by such Contributor itself or anyone acting on such
50 | Contributor's behalf. Contributions do not include additions to the
51 | Program which: (i) are separate modules of software distributed in
52 | conjunction with the Program under their own license agreement, and (ii)
53 | are not derivative works of the Program.
54 |
55 | "Contributor" means any person or entity that distributes
56 | the Program.
57 |
58 | "Licensed Patents" mean patent claims licensable by a
59 | Contributor which are necessarily infringed by the use or sale of its
60 | Contribution alone or when combined with the Program.
61 |
62 | "Program" means the Contributions distributed in accordance
63 | with this Agreement.
64 |
65 | "Recipient" means anyone who receives the Program under
66 | this Agreement, including all Contributors.
67 |
68 | 2. GRANT OF RIGHTS
69 |
70 | a) Subject to the terms of this Agreement, each
71 | Contributor hereby grants Recipient a non-exclusive, worldwide,
72 | royalty-free copyright license to reproduce, prepare derivative works
73 | of, publicly display, publicly perform, distribute and sublicense the
74 | Contribution of such Contributor, if any, and such derivative works, in
75 | source code and object code form.
76 |
77 | b) Subject to the terms of this Agreement, each
78 | Contributor hereby grants Recipient a non-exclusive, worldwide,
79 | royalty-free patent license under Licensed Patents to make, use, sell,
80 | offer to sell, import and otherwise transfer the Contribution of such
81 | Contributor, if any, in source code and object code form. This patent
82 | license shall apply to the combination of the Contribution and the
83 | Program if, at the time the Contribution is added by the Contributor,
84 | such addition of the Contribution causes such combination to be covered
85 | by the Licensed Patents. The patent license shall not apply to any other
86 | combinations which include the Contribution. No hardware per se is
87 | licensed hereunder.
88 |
89 | c) Recipient understands that although each Contributor
90 | grants the licenses to its Contributions set forth herein, no assurances
91 | are provided by any Contributor that the Program does not infringe the
92 | patent or other intellectual property rights of any other entity. Each
93 | Contributor disclaims any liability to Recipient for claims brought by
94 | any other entity based on infringement of intellectual property rights
95 | or otherwise. As a condition to exercising the rights and licenses
96 | granted hereunder, each Recipient hereby assumes sole responsibility to
97 | secure any other intellectual property rights needed, if any. For
98 | example, if a third party patent license is required to allow Recipient
99 | to distribute the Program, it is Recipient's responsibility to acquire
100 | that license before distributing the Program.
101 |
102 | d) Each Contributor represents that to its knowledge it
103 | has sufficient copyright rights in its Contribution, if any, to grant
104 | the copyright license set forth in this Agreement.
105 |
106 | 3. REQUIREMENTS
107 |
108 | A Contributor may choose to distribute the Program in object code
109 | form under its own license agreement, provided that:
110 |
111 | a) it complies with the terms and conditions of this
112 | Agreement; and
113 |
114 | b) its license agreement:
115 |
116 | i) effectively disclaims on behalf of all Contributors
117 | all warranties and conditions, express and implied, including warranties
118 | or conditions of title and non-infringement, and implied warranties or
119 | conditions of merchantability and fitness for a particular purpose;
120 |
121 | ii) effectively excludes on behalf of all Contributors
122 | all liability for damages, including direct, indirect, special,
123 | incidental and consequential damages, such as lost profits;
124 |
125 | iii) states that any provisions which differ from this
126 | Agreement are offered by that Contributor alone and not by any other
127 | party; and
128 |
129 | iv) states that source code for the Program is available
130 | from such Contributor, and informs licensees how to obtain it in a
131 | reasonable manner on or through a medium customarily used for software
132 | exchange.
133 |
134 | When the Program is made available in source code form:
135 |
136 | a) it must be made available under this Agreement; and
137 |
138 | b) a copy of this Agreement must be included with each
139 | copy of the Program.
140 |
141 | Contributors may not remove or alter any copyright notices contained
142 | within the Program.
143 |
144 | Each Contributor must identify itself as the originator of its
145 | Contribution, if any, in a manner that reasonably allows subsequent
146 | Recipients to identify the originator of the Contribution.
147 |
148 | 4. COMMERCIAL DISTRIBUTION
149 |
150 | Commercial distributors of software may accept certain
151 | responsibilities with respect to end users, business partners and the
152 | like. While this license is intended to facilitate the commercial use of
153 | the Program, the Contributor who includes the Program in a commercial
154 | product offering should do so in a manner which does not create
155 | potential liability for other Contributors. Therefore, if a Contributor
156 | includes the Program in a commercial product offering, such Contributor
157 | ("Commercial Contributor") hereby agrees to defend and
158 | indemnify every other Contributor ("Indemnified Contributor")
159 | against any losses, damages and costs (collectively "Losses")
160 | arising from claims, lawsuits and other legal actions brought by a third
161 | party against the Indemnified Contributor to the extent caused by the
162 | acts or omissions of such Commercial Contributor in connection with its
163 | distribution of the Program in a commercial product offering. The
164 | obligations in this section do not apply to any claims or Losses
165 | relating to any actual or alleged intellectual property infringement. In
166 | order to qualify, an Indemnified Contributor must: a) promptly notify
167 | the Commercial Contributor in writing of such claim, and b) allow the
168 | Commercial Contributor to control, and cooperate with the Commercial
169 | Contributor in, the defense and any related settlement negotiations. The
170 | Indemnified Contributor may participate in any such claim at its own
171 | expense.
172 |
173 | For example, a Contributor might include the Program in a commercial
174 | product offering, Product X. That Contributor is then a Commercial
175 | Contributor. If that Commercial Contributor then makes performance
176 | claims, or offers warranties related to Product X, those performance
177 | claims and warranties are such Commercial Contributor's responsibility
178 | alone. Under this section, the Commercial Contributor would have to
179 | defend claims against the other Contributors related to those
180 | performance claims and warranties, and if a court requires any other
181 | Contributor to pay any damages as a result, the Commercial Contributor
182 | must pay those damages.
183 |
184 | 5. NO WARRANTY
185 |
186 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
191 | responsible for determining the appropriateness of using and
192 | distributing the Program and assumes all risks associated with its
193 | exercise of rights under this Agreement , including but not limited to
194 | the risks and costs of program errors, compliance with applicable laws,
195 | damage to or loss of data, programs or equipment, and unavailability or
196 | interruption of operations.
197 |
198 | 6. DISCLAIMER OF LIABILITY
199 |
200 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
208 |
209 | 7. GENERAL
210 |
211 | If any provision of this Agreement is invalid or unenforceable under
212 | applicable law, it shall not affect the validity or enforceability of
213 | the remainder of the terms of this Agreement, and without further action
214 | by the parties hereto, such provision shall be reformed to the minimum
215 | extent necessary to make such provision valid and enforceable.
216 |
217 | If Recipient institutes patent litigation against any entity
218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the
219 | Program itself (excluding combinations of the Program with other
220 | software or hardware) infringes such Recipient's patent(s), then such
221 | Recipient's rights granted under Section 2(b) shall terminate as of the
222 | date such litigation is filed.
223 |
224 | All Recipient's rights under this Agreement shall terminate if it
225 | fails to comply with any of the material terms or conditions of this
226 | Agreement and does not cure such failure in a reasonable period of time
227 | after becoming aware of such noncompliance. If all Recipient's rights
228 | under this Agreement terminate, Recipient agrees to cease use and
229 | distribution of the Program as soon as reasonably practicable. However,
230 | Recipient's obligations under this Agreement and any licenses granted by
231 | Recipient relating to the Program shall continue and survive.
232 |
233 | Everyone is permitted to copy and distribute copies of this
234 | Agreement, but in order to avoid inconsistency the Agreement is
235 | copyrighted and may only be modified in the following manner. The
236 | Agreement Steward reserves the right to publish new versions (including
237 | revisions) of this Agreement from time to time. No one other than the
238 | Agreement Steward has the right to modify this Agreement. The Eclipse
239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may
240 | assign the responsibility to serve as the Agreement Steward to a
241 | suitable separate entity. Each new version of the Agreement will be
242 | given a distinguishing version number. The Program (including
243 | Contributions) may always be distributed subject to the version of the
244 | Agreement under which it was received. In addition, after a new version
245 | of the Agreement is published, Contributor may elect to distribute the
246 | Program (including its Contributions) under the new version. Except as
247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
248 | rights or licenses to the intellectual property of any Contributor under
249 | this Agreement, whether expressly, by implication, estoppel or
250 | otherwise. All rights in the Program not expressly granted under this
251 | Agreement are reserved.
252 |
253 | This Agreement is governed by the laws of the State of New York and
254 | the intellectual property laws of the United States of America. No party
255 | to this Agreement will bring a legal action under this Agreement more
256 | than one year after the cause of action arose. Each party waives its
257 | rights to a jury trial in any resulting litigation.
258 |
259 |
260 |
261 |
262 |
--------------------------------------------------------------------------------
/src/stockings/core.clj:
--------------------------------------------------------------------------------
1 | (ns stockings.core
2 | "Get current and historical stock quotes, current currency exchange rates,
3 | and info on industry sectors, industries, companies, and stocks
4 | from Yahoo! Finance."
5 | {:author "Filippo Tampieri "}
6 | (:use [clojure.string :only (split join lower-case)]
7 | [clojure.contrib.def :only (defvar defvar-)]
8 | [clojure.contrib.json :only (read-json)])
9 | (:require [clj-http.client :as client]
10 | [stockings.utils :as yql])
11 | (:import (org.joda.time LocalDate LocalTime DateTimeZone)
12 | (java.util TimeZone)))
13 |
14 | ;;; NOTES
15 |
16 | ;;; Yahoo! Finance web services do not recognize stock symbols prefixed with
17 | ;;; a stock exchange, so we always strip it before passing the symbols
18 | ;;; to the web queries.
19 |
20 | ;;;
21 | ;;; Utilities
22 | ;;;
23 |
24 | (defn prefix-stock-symbol
25 | "Takes the symbols for a stock exchange and a stock and combines them
26 | to create an exchange-prefixed stock symbol (e.g. \"NASDAQ\" and
27 | \"YHOO\" are combined into \"NASDAQ:YHOO\")."
28 | [^String exchange-symbol ^String stock-symbol]
29 | (if exchange-symbol
30 | (str exchange-symbol ":" stock-symbol)
31 | stock-symbol))
32 |
33 | (defn explode-stock-symbol
34 | "Takes a stock symbol and explodes it into its exchange prefix and
35 | bare symbol parts, returning a vector with two entries. If the
36 | exchange prefix is missing, the first entry in the vector will be
37 | `nil`."
38 | [^String stock-symbol]
39 | (let [[s exchange symbol] (re-matches #"(.+):(.+)" stock-symbol)]
40 | (if s
41 | [exchange symbol]
42 | [nil stock-symbol])))
43 |
44 | (defn bare-stock-symbol
45 | "Returns a stock symbol stripped of the stock exchange prefix, if any."
46 | [^String stock-symbol]
47 | (second (explode-stock-symbol stock-symbol)))
48 |
49 | (defn get-largest-lte
50 | "Returns the value mapped to the largest key that is less than or
51 | equal to the supplied key; returns `not-found` or `nil` if a matching key
52 | cannot be found. The supplied map should be a sorted map. You can use
53 | this function to lookup historical stock quotes by date and still get
54 | a valid quote when the date falls on a weekend or holiday. Use
55 | `to-sorted-map` to create a sorted map from a sequence of historical quotes."
56 | ([^clojure.lang.Sorted map key] (get-largest-lte map key nil))
57 | ([^clojure.lang.Sorted map key not-found]
58 | (if-let [lte-part (rsubseq map <= key)]
59 | (val (first lte-part))
60 | not-found)))
61 |
62 | (defn to-sorted-map
63 | "Builds a sorted map from the supplied collection. The supplied `get-key`
64 | function is used to compute the key corresponding to a given item
65 | in the collection."
66 | [get-key xs]
67 | (apply sorted-map (mapcat (fn [x] [(get-key x) x]) xs)))
68 |
69 | ;;; NOTE: the date and time values returned by queries that are
70 | ;;; resolved to http://download.yahoo.finance.com/d/quotes.csv are
71 | ;;; wrong. It seems that the date is given for the UTC zone, but the
72 | ;;; time is given for the America/New_York time zone.
73 | ;;; The following code addresses the problem and assembles a correct
74 | ;;; DateTime object in UTC from the date and time given.
75 | ;;; You will want to use it if you are assembling a time stamp from
76 | ;;; the LastTradeDate and LastTradeTime fields in the stock quotes
77 | ;;; fetched by get-quotes or just use parse-last-trade-date-time.
78 | ;;; WARNING: use it only for data retrieved from the quotes.csv
79 | ;;; service above!
80 |
81 | (defvar- nyc-date-time-zone
82 | (DateTimeZone/forTimeZone (TimeZone/getTimeZone "America/New_York")))
83 |
84 | (defn get-correct-date-time [^LocalDate date ^LocalTime time]
85 | (if (and date time)
86 | (let [wrong-date-time (.toDateTime date time nyc-date-time-zone)]
87 | (.withFields (.toDateTime wrong-date-time DateTimeZone/UTC) date))))
88 |
89 | ;;;
90 | ;;; Get current quotes
91 | ;;;
92 |
93 | (defvar- quote-parse-map
94 | {:symbol identity
95 | :Name identity
96 | :PERatio yql/parse-double
97 | :EarningsShare yql/parse-double
98 | :EPSEstimateNextYear yql/parse-double
99 | :PercebtChangeFromYearHigh yql/parse-double ; typo is actually from data source
100 | :ChangeFromTwoHundreddayMovingAverage yql/parse-double
101 | :TwoHundreddayMovingAverage yql/parse-double
102 | :ChangeinPercent yql/parse-percent
103 | :Bid yql/parse-double
104 | :DaysLow yql/parse-double
105 | :DividendShare yql/parse-double
106 | :ChangeFromYearLow yql/parse-double
107 | :MarketCapitalization yql/parse-double
108 | :YearLow yql/parse-double
109 | :PriceSales yql/parse-double
110 | :ShortRatio yql/parse-double
111 | :PercentChange yql/parse-percent
112 | :EPSEstimateCurrentYear yql/parse-double
113 | :FiftydayMovingAverage yql/parse-double
114 | :ChangeRealtime yql/parse-double
115 | :PercentChangeFromFiftydayMovingAverage yql/parse-percent
116 | :LastTradePriceOnly yql/parse-double
117 | :ExDividendDate yql/parse-date
118 | :DividendPayDate yql/parse-date
119 | :Ask yql/parse-double
120 | :AskRealtime yql/parse-double
121 | :OneyrTargetPrice yql/parse-double
122 | :DividendYield yql/parse-percent
123 | :PercentChangeFromYearLow yql/parse-percent
124 | :PriceBook yql/parse-double
125 | :ChangeFromFiftydayMovingAverage yql/parse-double
126 | :AverageDailyVolume yql/parse-long
127 | :Open yql/parse-double
128 | :PercentChangeFromTwoHundreddayMovingAverage yql/parse-percent
129 | :BidRealTime yql/parse-double
130 | :BookValue yql/parse-double
131 | :LastTradeTime yql/parse-time
132 | :LastTradeDate yql/parse-date
133 | :Change yql/parse-double
134 | :Volume yql/parse-long
135 | :PEGRatio yql/parse-double
136 | :StockExchange identity
137 | :ChangeFromYearHigh yql/parse-double
138 | :DaysHigh yql/parse-double
139 | :YearHigh yql/parse-double
140 | :EBITDA yql/parse-double
141 | :Symbol identity
142 | :PreviousClose yql/parse-double
143 | :PERatioRealtime yql/parse-double
144 | :PriceEPSEstimateNextYear yql/parse-double
145 | :EPSEstimateNextQuarter yql/parse-double
146 | :PriceEPSEstimateCurrentYear yql/parse-double}
147 | "A map from the keys of a raw stock quote to the parsers used to parse
148 | the corresponding value strings of the raw stock quote into useful
149 | typed values (ints, doubles, dates, etc.).")
150 |
151 | (defvar raw-quote-keys (keys quote-parse-map)
152 | "A list of all the keys available in a raw stock quote. A custom stock
153 | quote can be created by supplying `get-quote` with a parser capable of
154 | extracting the value corresponding to a chosen subset of these keys
155 | from a raw stock quote and packaging them into the desired result
156 | structure.")
157 |
158 | (defn parse-quote-item
159 | "Looks up the value of a supplied key into a raw stock quote and
160 | converts it from a string into a more useful type (int, double, date,
161 | time, or keyword depending on the key). It returns `nil` if it cannot
162 | find a valid value."
163 | [raw-quote k]
164 | (if-let [value-parser (get quote-parse-map k)]
165 | (value-parser (get raw-quote k))))
166 |
167 | (defn parse-last-trade-date-time
168 | "Looks up the last trade date and time in the supplied raw stock quote
169 | and combines them into an `org.joda.time.DateTime` object in UTC. It
170 | works around an inconsistency in the Yahoo! Finance data where the
171 | last trade date is represented in UTC while the last trade time seems
172 | to be using the North American East Coast time zone. It returns `nil`
173 | if a valid `DateTime` object cannot be created."
174 | [raw-quote]
175 | (let [date (parse-quote-item raw-quote :LastTradeDate)
176 | time (parse-quote-item raw-quote :LastTradeTime)]
177 | (get-correct-date-time date time)))
178 |
179 | (defn build-quote-parser
180 | "Returns a parser that can be passed to `get-quote` and `get-quotes` to
181 | get custom stock quotes. The generated parser will return a stock quote
182 | in the form of a map with a set of desired keys. The builder expects a
183 | map from the set of desired keys to the corresponding keys in the raw
184 | stock quote (see `raw-quote-keys` for a list of available keys)."
185 | [key-map]
186 | (fn [raw-quote]
187 | (apply hash-map
188 | (mapcat (fn [[k v]] [k (parse-quote-item raw-quote v)]) key-map))))
189 |
190 | (defvar- default-key-map
191 | {:symbol :symbol
192 | :name :Name
193 | :last :LastTradePriceOnly
194 | :open :Open
195 | :previous-close :PreviousClose
196 | :high :DaysHigh
197 | :low :DaysLow
198 | :volume :Volume})
199 |
200 | (defvar- default-quote-parser* (build-quote-parser default-key-map))
201 |
202 | (defn default-quote-parser
203 | "The quote parser used by `get-quote` and `get-quotes` when a custom quote
204 | parser is not provided. It returns a stock quote in the form of a map
205 | with :symbol, :name, :last-date-time, :last, :open, :previous-close,
206 | :high, :low, and :volume keys."
207 | [raw-quote]
208 | (let [stock-quote (default-quote-parser* raw-quote)]
209 | (assoc stock-quote
210 | :last-date-time (parse-last-trade-date-time raw-quote))))
211 |
212 | (defn- wrap-error-check
213 | "Augments a stock quote parser to check the raw quote for errors
214 | (typically arising when an invalid stock symbol is required of
215 | `get-quote`). The resulting parser will return `nil` if an error is
216 | found; otherwise, it will invoke the original parser."
217 | [parser]
218 | (fn [r]
219 | (if-not (:ErrorIndicationreturnedforsymbolchangedinvalid r)
220 | (parser r))))
221 |
222 | (defn get-quotes
223 | "Returns a sequence of stock quotes corresponding to the given stock
224 | symbols. If the first parameter is a function, it uses it as a
225 | parser to turn a raw stock quote (provided by Yahoo! Finance) to a
226 | custom stock quote structure; otherwise, it uses `default-quote-parser`."
227 | [parser & stock-symbols]
228 | (let [[parser stock-symbols] (if (fn? parser)
229 | [parser stock-symbols]
230 | [default-quote-parser (cons parser
231 | stock-symbols)])]
232 | (if stock-symbols
233 | (let [query (str "select * from yahoo.finance.quotes where symbol in "
234 | (yql/yql-string-list (map bare-stock-symbol stock-symbols)))
235 | result (yql/submit-yql-query query)]
236 | (yql/map-parser (wrap-error-check parser) (:quote result))))))
237 |
238 | (defn get-quote
239 | "Returns the stock quote corresponding to the given stock symbol.
240 | If the first parameter is a function, it uses it as a parser to turn
241 | a raw stock quote (provided by Yahoo! Finance) to a custom stock quote
242 | structure; otherwise, it uses `default-quote-parser`."
243 | ([parser stock-symbol] (first (get-quotes parser stock-symbol)))
244 | ([stock-symbol] (first (get-quotes stock-symbol))))
245 |
246 | ;;;
247 | ;;; Get historical quotes
248 | ;;;
249 |
250 | ;;; FIXME: consider using type annotations for the double and int/long fields!
251 | (defrecord HistoricalQuote [^LocalDate date open high low close volume])
252 |
253 | (defn- parse-historical-quote [r]
254 | (let [date (yql/parse-date (:date r))
255 | open (yql/parse-double (:Open r))
256 | high (yql/parse-double (:High r))
257 | low (yql/parse-double (:Low r))
258 | close (yql/parse-double (:Close r))
259 | volume (yql/parse-long (:Volume r))]
260 | (HistoricalQuote. date open high low close volume)))
261 |
262 | (defn get-historical-quotes
263 | "Returns a sequence of historical stock quotes corresponding to the
264 | supplied stock symbol, one quote per day between the supplied start
265 | and end dates (as `org.joda.time.LocalDate` objects). Quotes
266 | corresponding to dates falling on weekends and holidays are not
267 | included in the resulting sequence. If quotes for the given symbol
268 | or period cannot be found, it returns `nil`.
269 |
270 | **NOTE:** The Yahoo! Finance service limits the requested period to about
271 | 18 months. If the supplied date range describes a period longer than that
272 | limit, the query returns `nil`."
273 | [^String stock-symbol ^LocalDate start-date ^LocalDate end-date]
274 | (let [query (str "select * from yahoo.finance.historicaldata where symbol = "
275 | (yql/yql-string (bare-stock-symbol stock-symbol))
276 | " and startDate = " (yql/yql-string start-date)
277 | " and endDate = " (yql/yql-string end-date))
278 | result (yql/submit-yql-query query)]
279 | (if result
280 | (yql/map-parser parse-historical-quote (:quote result)))))
281 |
282 | ;;;
283 | ;;; Get stocks
284 | ;;;
285 |
286 | (defn- parse-stock [r]
287 | ;; If the requested stock symbol could not be found, the
288 | ;; :CompanyName key will be missing (in this case, the parser returns nil).
289 | (if (:CompanyName r)
290 | (let [stock-symbol (:symbol r)
291 | company-name (:CompanyName r)
292 | start-date (yql/parse-date (:start r))
293 | end-date (yql/parse-date (:end r))
294 | sector (:Sector r)
295 | industry (:Industry r)
296 | full-time-employees (yql/parse-int (:FullTimeEmployees r))]
297 | {:symbol stock-symbol
298 | :name company-name
299 | :start-date start-date
300 | :end-date end-date
301 | :sector sector
302 | :industry industry
303 | :full-time-employees full-time-employees})))
304 |
305 | (defn get-stocks
306 | "Returns a sequence of stock descriptions for the supplied stock symbols.
307 | A stock description is a map with :symbol, :name, :start-date, :end-date,
308 | :sector, :industry, and :full-time-employees keys.
309 | If data for a symbol cannot be found that description will be `nil`."
310 | [& stock-symbols]
311 | (if stock-symbols
312 | (let [query (str "select * from yahoo.finance.stocks where symbol in "
313 | (yql/yql-string-list (map bare-stock-symbol stock-symbols)))
314 | result (yql/submit-yql-query query)]
315 | (yql/map-parser parse-stock (:stock result)))))
316 |
317 | (defn get-stock
318 | "Returns the stock description for the supplied stock symbols.
319 | This description is a map with :symbol, :name, :start-date, :end-date,
320 | :sector, :industry, and :full-time-employees keys.
321 | If data for a symbol cannot be found it returns `nil`."
322 | [stock-symbol]
323 | (first (get-stocks stock-symbol)))
324 |
325 | ;;;
326 | ;;; Industries
327 | ;;;
328 |
329 | (defn- parse-industry-sector [{:keys [name industry]}]
330 | {:name name, :industries (if (vector? industry) industry [industry])})
331 |
332 | (defn get-industry-sectors
333 | "Returns a sequence of industry sectors. Each sector is a map with
334 | :name and :industries keys. The value of the latter is a vector of
335 | maps corresponding to the industries for that sector. Each industry
336 | is a map with :id and :name fields. The id value can be passed to
337 | `get-industry` to get a list of the companies for a given industry."
338 | []
339 | (let [query "select * from yahoo.finance.sectors"
340 | result (yql/submit-yql-query query)]
341 | (yql/map-parser parse-industry-sector (:sector result))))
342 |
343 | (defn- parse-industry [{:keys [id name company]}]
344 | (if id
345 | {:id id, :name name, :companies (if (vector? company) company [company])}))
346 |
347 | (defn get-industries
348 | "Returns a sequence of the industries corresponding to the supplied
349 | industry ids. Each industry is returned as a map with :id, :name,
350 | and :companies keys. The value of the latter is a vector of maps
351 | corresponding to the companies in that industry. Each company is
352 | a map with :name and :symbol keys. The symbol value can be passed to
353 | `get-quote` or `get-historical-quotes` to get stock quotes for that company.
354 | If the industry for a given id cannot be found, its place in the result
355 | sequence will be nil."
356 | [& industry-ids]
357 | (if industry-ids
358 | (let [query (str "select * from yahoo.finance.industry where id in "
359 | (yql/yql-string-list industry-ids))
360 | result (yql/submit-yql-query query)]
361 | (yql/map-parser parse-industry (:industry result)))))
362 |
363 | (defn get-industry
364 | "Returns the industry corresponding to the supplied industry ids.
365 | The result is a map with :id, :name, and :companies keys. The value
366 | of the latter is a vector of maps corresponding to the companies in
367 | that industry. Each company is a map with :name and :symbol keys.
368 | The symbol value can be passed to `get-quote` or `get-historical-quotes`
369 | to get stock quotes for that company.
370 | If the industry for the supplied id cannot be found, it returns `nil`."
371 | [industry-id]
372 | (first (get-industries industry-id)))
373 |
374 | ;;;
375 | ;;; Get current exchange rate
376 | ;;;
377 |
378 | (defn- parse-exchange-rate [r]
379 | ;; If the requested currency pair could not be found, the
380 | ;; value of the :Name key will be suffixed with "=X"
381 | ;; (in this case, the parser returns nil).
382 | (if-not (re-matches #".*=X" (:Name r))
383 | (let [id (:id r)
384 | base-currency (keyword (lower-case (subs id 0 3)))
385 | quote-currency (keyword (lower-case (subs id 3 6)))
386 | rate (yql/parse-double (:Rate r))
387 | ask (yql/parse-double (:Ask r))
388 | bid (yql/parse-double (:Bid r))
389 | date (yql/parse-date (:Date r))
390 | time (yql/parse-time (:Time r))
391 | date-time (get-correct-date-time date time)]
392 | {:base base-currency
393 | :quote quote-currency
394 | :rate rate
395 | :ask ask
396 | :bid bid
397 | :date-time date-time})))
398 |
399 | (defn get-exchange-rates
400 | "Returns a sequence of currency exchange rate for all the supplied
401 | currency pairs. Each currency pair is given as a vector of two items:
402 | base and quote currencies, specified as keywords or strings using the
403 | ISO 4217 3-letter designators (e.g. `[:usd :eur]`).
404 | The items in the returned sequence are maps with :base, :quote, :rate,
405 | :ask, :bid, and :date-time keys.
406 | If the exchange rate for a given currency pair cannot be found, its
407 | place in the result sequence will be `nil`."
408 | [& currency-pairs]
409 | (if currency-pairs
410 | (let [pairs (map (fn [[base-currency quote-currency]]
411 | (str (name base-currency)
412 | (name quote-currency))) currency-pairs)
413 | query (str "select * from yahoo.finance.xchange where pair in "
414 | (yql/yql-string-list pairs))
415 | result (yql/submit-yql-query query)]
416 | (yql/map-parser parse-exchange-rate (:rate result)))))
417 |
418 | (defn get-exchange-rate
419 | "Returns the currency exchange rate for the supplied currency pairs.
420 | The currency pair is given as two separate parameters: base and quote
421 | currencies, specified as keywords or strings using the ISO 4217
422 | 3-letter designators (e.g. `(get-exchange-rate :usd :eur)`).
423 | The result is a map with :base, :quote, :rate, :ask, :bid, and
424 | :date-time keys.
425 | If the exchange rate for the given currency pair cannot be found, it
426 | returns `nil`."
427 | [base-currency quote-currency]
428 | (first (get-exchange-rates [base-currency quote-currency])))
429 |
430 | ;;;
431 | ;;; Get stock symbol suggestion
432 | ;;;
433 |
434 | (defn- strip-wrapper [^String s]
435 | (let [[s json-string] (re-matches #"YAHOO\.Finance.SymbolSuggest\.ssCallback\((.*)\)" s)]
436 | json-string))
437 |
438 | (defn get-symbol-suggestion
439 | "Returns zero, one or more suggestions for the stock symbols corresponding
440 | to the supplied company name. Company name prefixes will return results
441 | as well. Note that large companies may be traded on several exchanges and
442 | may thus result in a long list of suggestions."
443 | [^String company-name]
444 | (let [params {:query company-name
445 | :callback "YAHOO.Finance.SymbolSuggest.ssCallback"}
446 | response (client/get "http://autoc.finance.yahoo.com/autoc"
447 | {:query-params params})
448 | status (:status response)]
449 | (if (not= status 200)
450 | (throw (Exception. (str status))))
451 | (let [result (-> response :body strip-wrapper read-json :ResultSet :Result)]
452 | (if-not (empty? result) result))))
453 |
454 |
--------------------------------------------------------------------------------