1
0

finance.lisp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. (in-package #:chatikbot)
  2. (defparameter +yahoo-url+ "https://query1.finance.yahoo.com/v7/finance/quote?lang=en-US&region=US&corsDomain=finance.yahoo.com&fields=regularMarketPrice" "Yahoo Finance API endpoint")
  3. (defparameter +brent-url+ "http://www.cmegroup.com/CmeWS/mvc/Quotes/Future/424/G")
  4. (defvar *rate-pairs* '("USDRUB=X" "EURRUB=X" "GBPRUB=X"))
  5. (defparameter +chart-ranges+ (list (cons "day" (* 24 60))
  6. (cons "week" (* 7 24 60))
  7. (cons "month" (* 30 24 60))
  8. (cons "quarter" (* 91 24 60))
  9. (cons "year" (* 365 24 60))))
  10. (defun get-rates (&optional (pairs *rate-pairs*))
  11. (let ((response (agets (json-request
  12. +yahoo-url+
  13. :parameters `(("symbols" . ,(format nil "~{~A~^,~}" pairs))))
  14. "quoteResponse")))
  15. (when (aget "error" response)
  16. (error "Error in rates request"))
  17. (loop for rate in (agets response "result")
  18. collect (cons (or (aget "shortName" rate) (aget "symbol" rate))
  19. (aget "regularMarketPrice" rate)))))
  20. (defun get-brent ()
  21. (handler-case
  22. (let ((last (read-from-string
  23. (aget "last" (first (aget "quotes" (json-request +brent-url+)))))))
  24. (when (numberp last)
  25. last))
  26. (error (e) (log:error e))))
  27. (defparameter +btc-e-usd-url+ "https://btc-e.com/api/2/btc_usd/ticker" "BTC-e BTC/USD ticker url")
  28. (defun get-btc-e ()
  29. (handler-case
  30. (aget "last" (aget "ticker" (json-request +btc-e-usd-url+)))
  31. (error (e) (log:error e))))
  32. (defparameter +coindesk-btc-usd-url+ "https://api.coindesk.com/site/headerdata.json?currency=BTC" "coindesk.com api endpoint")
  33. (defun get-coindesk-btc-usd ()
  34. (handler-case
  35. (agets (json-request +coindesk-btc-usd-url+)
  36. "bpi" "USD" "rate_float")
  37. (error (e) (log:error "~A" e))))
  38. (defun get-serie (series idx)
  39. (loop for row in series
  40. when (elt row idx)
  41. collect (list (first row) (elt row idx))))
  42. (defun range (seq &key (key 'identity))
  43. (loop
  44. for item in seq
  45. for value = (funcall key item)
  46. minimizing value into min
  47. maximizing value into max
  48. finally (return (values min max))))
  49. (defvar *chart-points* 800 "Data points in chart")
  50. (defun make-chart (series names)
  51. (let* ((r (multiple-value-list (range series :key #'car)))
  52. (fmt (if (<= (- (second r) (first r))
  53. (* 60 60 36))
  54. '((:hour 2) ":" (:min 2))
  55. '((:day 2) "." (:month 2) " " (:hour 2) ":" (:min 2)))))
  56. (adw-charting:with-line-chart ((+ 90 *chart-points*)
  57. (floor (* 3 *chart-points*) 4))
  58. (loop for serie in names
  59. for idx from 1
  60. do (adw-charting:add-series serie (get-serie series idx)))
  61. (adw-charting:set-axis
  62. :x "Time" :draw-gridlines-p t
  63. :label-formatter #'(lambda (v)
  64. (local-time:format-timestring
  65. nil
  66. (local-time:unix-to-timestamp v)
  67. :format fmt)))
  68. (adw-charting:set-axis :y "RUB" :draw-gridlines-p t :label-formatter "~,2F")
  69. (adw-charting:save-file "chart.png"))))
  70. ;; Database
  71. (def-db-init
  72. (db-execute "create table if not exists finance (ts, usd, eur, gbp, brent, btc)")
  73. (db-execute "create index if not exists fin_ts_ids on finance (ts)"))
  74. (defun db/add-finance (ts usd eur gbp brent btc)
  75. (db-execute "insert into finance (ts, usd, eur, gbp, brent, btc) values (?, ?, ?, ?, ?, ?)"
  76. ts usd eur gbp brent btc))
  77. (defun db/get-last-finance ()
  78. (values-list (first (db-select "select ts, usd, eur, gbp, brent, btc from finance order by ts desc limit 1"))))
  79. (defparameter +finance-db-map+ '(("usd" . "USD/RUB")
  80. ("eur" . "EUR/RUB")
  81. ("gbp" . "GBP/RUB")
  82. ("brent" . "Brent")
  83. ("btc" . "BTC/USD")))
  84. (defun db/get-series (after-ts &optional (fields '("usd" "eur" "gbp" "brent")) (avg 60))
  85. (when fields
  86. (let ((sql (format nil
  87. "select ts/~a*~:*~a,~{avg(~a) as ~:*~a~^,~} from finance where ts >= ? group by ts/~a order by ts"
  88. avg fields avg)))
  89. (values
  90. (db-select sql (local-time:timestamp-to-unix after-ts))
  91. (sublis +finance-db-map+ fields :test #'equal)))))
  92. ;; Cron
  93. (defcron process-rates ()
  94. (let ((ts (local-time:timestamp-to-unix (local-time:now)))
  95. (rates (get-rates))
  96. (brent (get-brent))
  97. (btc (get-coindesk-btc-usd)))
  98. (db/add-finance ts (aget "USDRUB=X" rates) (aget "EUR/RUB" rates) (aget "GBP/RUB" rates) brent btc)))
  99. ;;; Hooks
  100. (def-message-cmd-handler handler-rates (:rates)
  101. (multiple-value-bind (ts usd eur gbp brent btc) (db/get-last-finance)
  102. (bot-send-message chat-id
  103. (format nil "Зеленый *~,2F*, гейро *~,2F*, британец *~,2F*, чёрная *~,2F*, btc *~,2F* @ _~A_"
  104. usd eur gbp brent btc
  105. (format-ts (local-time:unix-to-timestamp ts)))
  106. :parse-mode "Markdown")))
  107. (def-message-cmd-handler handler-charts (:charts)
  108. (telegram-send-chat-action chat-id "upload_photo")
  109. (let* ((args (mapcar 'string-downcase args))
  110. (all-fields (mapcar #'car +finance-db-map+))
  111. (fields (or (intersection args all-fields :test 'equal) all-fields))
  112. (day-range (some #'(lambda (a) (aget a +chart-ranges+)) args))
  113. (number (some #'(lambda (a) (parse-integer a :junk-allowed t)) args))
  114. (avg (* 60 (cond
  115. (day-range (round day-range *chart-points*))
  116. (number)
  117. (:otherwise 1))))
  118. (after-ts (local-time:timestamp- (local-time:now)
  119. (* avg *chart-points*) :sec))
  120. (rates (multiple-value-list (db/get-last-finance)))
  121. (chart (apply #'make-chart (multiple-value-list
  122. (db/get-series after-ts fields avg)))))
  123. (telegram-send-photo chat-id chart
  124. :caption
  125. (format nil "Зеленый ~,2F, гейро ~,2F, британец ~,2F, чёрная ~,2F, btc ~,2F @ ~A"
  126. (elt rates 1) (elt rates 2) (elt rates 3) (elt rates 4) (elt rates 5)
  127. (format-ts (local-time:unix-to-timestamp (elt rates 0)))))))