;;; ;;; ;;; ;;; lem config ;;; ;;; (in-package :lem-user) (ql:quickload :edgar) (define-key lem-lisp-mode:*lisp-mode-keymap* "C-c C-v" 'clog-reload) (define-command clog-reload () (reload ()) ) ;;; ;;; ;;; ;;; edgar.asd ;;; ;;; (asdf:defsystem #:edgar :description "New CLOG System" :author "some@one.com" :license "BSD" :version "0.0.0" :serial t :entry-point "edgar:start-app" :depends-on (#:clog #:dexador #:cl-json) ; add clog plugins here as #:plugin for run time :components ((:file "edgar"))) (asdf:defsystem #:edgar/tools :defsystem-depends-on (:clog) :depends-on (#:edgar #:clog/tools) ; add clog plugins here as #:plugin/tools for design time :components ()) ;;; ;;; ;;; ;;; edgar.lisp ;;; ;;; ;; ====[ edgar - package ]=================================================================== (defpackage #:edgar (:use #:cl #:clog ) (:export start-app)) (in-package :edgar) (defun %start-app () (initialize 'on-new-window :static-root (merge-pathnames "./www/" (asdf:system-source-directory :edgar))) (set-on-new-window 'reload :path "/blah") (open-browser)) (defun %start-app () (initialize 'on-new-window :static-root (merge-pathnames "./www/" (asdf:system-source-directory :edgar))) (set-on-new-window 'reload :path "/blah") (open-browser)) (defun start-app () (bt:make-thread #'%start-app :name "Edgar CLOG thread") ) ;; page reload function ;; (defun reload (body) ;; (create-div body :content "no, no, no!") ;; ;; (url-replace (location obj) "/galleri") ;; ;; (url-replace body "/galleri") ;; ) ;; ====[ nfo ]=================================================================== ;; url: https://medium.com/@codingcamel/extracting-fundamental-stock-data-from-edgar-using-our-favorite-language-common-lisp-part-1-a3171d100dd4 ;; and: https://medium.com/@codingcamel/extracting-fundamental-stock-data-from-edgar-using-our-favorite-language-common-lisp-part-2-f28099de4b71 ;; and: https://medium.com/@codingcamel/extracting-fundamental-stock-data-from-edgar-using-our-favorite-language-common-lisp-part-3-5ea58d903b97 ;; sunday feb 03 ;; basic clog ui is up and running ;; just need to make it look nice ;; ;; ====[ code ]=================================================================== (defun on-new-window (body) (load-css (html-document body) "https://www.w3schools.com/w3css/4/w3.css") (let* ( (menu (create-div body :content "meh" :class "w3-light-green")) (content (create-div body :content "------==[ galleri - complicated ]==------" :class "w3-light-blue" :style "text-align:center; margin-top:40px;")) (btn-get-data (create-div menu :content "DOWNLOAD THE DATA")) (btn-dex (create-div menu :content "PRINT DEX")) (btn-json (create-div menu :content "PRINT json")) (btn-url (create-div menu :content "URL RELOAD")) ) (set-on-click btn-get-data (lambda (obj) (setf (css-class-name content) "w3-gren") (read-tickers) (setf (text content) "downloaded the data") ;; (url-replace (location obj) "/test") )) (set-on-click btn-dex (lambda (obj) (setf (css-class-name content) "w3-pink") (setf (text content) (out-dex)) )) (set-on-click btn-json (lambda (obj) (setf (css-class-name content) "w3-orange") (setf (text content) (out-json)) )) (set-on-click btn-url (lambda (obj) ;; (url-replace (location body) "/") (reload () ) )) (defun reload (obj) (create-div body :content "no, no, no!") (url-replace (location body) "/") ;; (url-replace body "/galleri") ) )) (defparameter *sec-headers* '( ("User-Agent" . "example@example.example") ) ) (defvar *company-info* (make-hash-table :test 'equal)) (defun read-tickers () "Get list of companies from Edgar and store them in the *company-info* hash table" (let* ( (tickers (dex:get "https://www.sec.gov/files/company_tickers.json" :headers *sec-headers*)) (response (cl-json:decode-json-from-string tickers)) ) (dolist (item response) (let* ( (ticker (cdr (assoc :TICKER (cdr item)))) (cik (cdr (assoc :CIK--STR (cdr item)))) (company (cdr (assoc :TITLE (cdr item)))) (value (list (cons 'company company) (cons 'cik cik))) ) (setf (gethash ticker *company-info*) value) )) )) (defun pad-cik (cik) "Pad CIK number so it is always 10 digits long" (format nil "~10,'0d" cik)) (defun get-stock-cik (ticker) "Get CIK number for a given ticker" (let ( (data (gethash ticker *company-info*)) ) (pad-cik (cdr (assoc 'cik data))) )) (defun get-company-name (ticker) "Get company name for a given ticker" (let ( (data (gethash ticker *company-info*)) ) (cdr (assoc 'company data)) )) (defun get-company-concept (ticker taxonomy concept) "Get company concept using a taxonomy and a concept" (cl-json:decode-json-from-string (dex:get (format nil "https://data.sec.gov/api/xbrl/companyconcept/CIK~a/~a/~a.json" (get-stock-cik ticker) taxonomy concept) :headers *sec-headers*))) ;;; We store our data in a variable called "data" ;; (setf data (get-company-concept "AAPL" "us-gaap" "EarningsPerShareBasic")) ;;; And now we traverse the nested alist until we get to our EPS information ;; (setf eps (loop for entry in ;; (cdr (assoc :+usd+/shares (cdr (assoc :units data)))) ;; when (equal (cdr (assoc :FORM entry)) "10-K") ;; collect (list (cdr (assoc :FY entry)) (cdr (assoc :VAL entry)))) ;; ) ;; ====[ nfo ]========================================================== ;; we can now do calculations in the repl against data ;; these examples can be used in a clog interface ;; grab all earnings per share values for aapl ;; (mapcar #'(lambda (x) (cadr x)) eps) ;; calculate average earnings per share for aapl ;; (/ (apply #'+ (mapcar #'(lambda (x) (cadr x)) eps)) ;; (length eps)) ;; ====[ test - out to file ]============================================= (defparameter *json* (cl-json:decode-json-from-string (dex:get "https://www.sec.gov/files/company_tickers.json" :headers '(("User-Agent" . "example@example.example"))) )) (defparameter *dex* (dex:get "https://www.sec.gov/files/company_tickers.json" :headers '(("User-Agent" . "example@example.example"))) ) (defun out-json () (cl-json:decode-json-from-string (dex:get "https://www.sec.gov/files/company_tickers.json" :headers '(("User-Agent" . "example@example.example"))) )) (defun out-dex () (dex:get "https://www.sec.gov/files/company_tickers.json" :headers '(("User-Agent" . "example@example.example"))) ) (defun tofile-json () (with-open-file (s "~/tmp/json.nfo" :direction :output :if-does-not-exist :create :if-exists :overwrite) (write *json* :stream s ) )) (defun tofile-dex () (with-open-file (s "~/tmp/dex.nfo" :direction :output :if-does-not-exist :create :if-exists :overwrite) (write *dex* :stream s) ))