1
0

finance.lisp 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. (in-package #:chatikbot)
  2. (defparameter +yahoo-url+ "https://query.yahooapis.com/v1/public/yql" "Yahoo Finance API endpoint")
  3. (defparameter +yahoo-query+ "select Name,Rate from yahoo.finance.xchange where pair in (~{\"~A\"~^,~})")
  4. (defparameter +brent-url+ "http://www.cmegroup.com/CmeWS/mvc/Quotes/Future/424/G")
  5. (defvar *rate-pairs* '("USDRUB" "EURRUB" "GBPRUB"))
  6. (defparameter +chart-ranges+ (list (cons "day" (* 24 60))
  7. (cons "week" (* 7 24 60))
  8. (cons "month" (* 30 24 60))
  9. (cons "quarter" (* 91 24 60))
  10. (cons "year" (* 365 24 60))))
  11. (defun get-rates (&optional (pairs *rate-pairs*))
  12. (let ((response (json-request
  13. +yahoo-url+
  14. :parameters (append '(("format" . "json")
  15. ("env" . "store://datatables.org/alltableswithkeys"))
  16. (list (cons "q" (format nil +yahoo-query+ pairs)))))))
  17. (when (aget "error" response)
  18. (error "Error in rates request"))
  19. (loop for rate in (aget "rate" (aget "results" (aget "query" response)))
  20. collect (cons (aget "Name" rate)
  21. (read-from-string (aget "Rate" rate))))))
  22. (defun get-brent ()
  23. (handler-case
  24. (let ((last (read-from-string
  25. (aget "last" (first (aget "quotes" (json-request +brent-url+)))))))
  26. (when (numberp last)
  27. last))
  28. (error (e) (log:error e))))
  29. (defparameter +btc-e-usd-url+ "https://btc-e.com/api/2/btc_usd/ticker" "BTC-e BTC/USD ticker url")
  30. (defun get-btc-e ()
  31. (handler-case
  32. (aget "last" (aget "ticker" (json-request +btc-e-usd-url+)))
  33. (error (e) (log:error e))))
  34. (defun get-serie (series idx)
  35. (loop for row in series
  36. when (elt row idx)
  37. collect (list (first row) (elt row idx))))
  38. (defun range (seq &key (key 'identity))
  39. (loop
  40. for item in seq
  41. for value = (funcall key item)
  42. minimizing value into min
  43. maximizing value into max
  44. finally (return (values min max))))
  45. (defvar *chart-points* 800 "Data points in chart")
  46. (defun make-chart (series names)
  47. (let* ((r (multiple-value-list (range series :key #'car)))
  48. (fmt (if (<= (- (second r) (first r))
  49. (* 60 60 36))
  50. '((:hour 2) ":" (:min 2))
  51. '((:day 2) "." (:month 2) " " (:hour 2) ":" (:min 2)))))
  52. (adw-charting:with-line-chart ((+ 90 *chart-points*)
  53. (floor (* 3 *chart-points*) 4))
  54. (loop for serie in names
  55. for idx from 1
  56. do (adw-charting:add-series serie (get-serie series idx)))
  57. (adw-charting:set-axis
  58. :x "Time" :draw-gridlines-p t
  59. :label-formatter #'(lambda (v)
  60. (local-time:format-timestring
  61. nil
  62. (local-time:unix-to-timestamp v)
  63. :format fmt)))
  64. (adw-charting:set-axis :y "RUB" :draw-gridlines-p t :label-formatter "~,2F")
  65. (adw-charting:save-file "chart.png"))))
  66. ;; Cron
  67. (defun process-rates ()
  68. (handler-case
  69. (let ((ts (local-time:timestamp-to-unix (local-time:now)))
  70. (rates (get-rates))
  71. (brent (get-brent))
  72. (btc (get-btc-e)))
  73. (db-add-finance ts (aget "USD/RUB" rates) (aget "EUR/RUB" rates) (aget "GBP/RUB" rates) brent btc))
  74. (error (e) (log:error "~A" e))))
  75. ;;; Hooks
  76. (def-message-cmd-handler handler-rates (:rates)
  77. (multiple-value-bind (ts usd eur gbp brent btc) (db-get-last-finance)
  78. (bot-send-message chat-id
  79. (format nil "Зеленый *~,2F*, гейро *~,2F*, британец *~,2F*, чёрная *~,2F*, btc *~,2F* @ _~A_"
  80. usd eur gbp brent btc
  81. (format-ts (local-time:unix-to-timestamp ts)))
  82. :parse-mode "Markdown")))
  83. (def-message-cmd-handler handler-charts (:charts)
  84. (telegram-send-chat-action chat-id "upload_photo")
  85. (let* ((args (mapcar 'string-downcase args))
  86. (all-fields (mapcar #'car +db-finance-map+))
  87. (fields (or (intersection args all-fields :test 'equal) all-fields))
  88. (day-range (some #'(lambda (a) (aget a +chart-ranges+)) args))
  89. (number (some #'(lambda (a) (parse-integer a :junk-allowed t)) args))
  90. (avg (* 60 (cond
  91. (day-range (round day-range *chart-points*))
  92. (number)
  93. (:otherwise 1))))
  94. (after-ts (local-time:timestamp- (local-time:now)
  95. (* avg *chart-points*) :sec))
  96. (rates (multiple-value-list (db-get-last-finance)))
  97. (chart (apply #'make-chart (multiple-value-list
  98. (db-get-series after-ts fields avg)))))
  99. (telegram-send-photo chat-id chart
  100. :caption
  101. (format nil "Зеленый ~,2F, гейро ~,2F, британец ~,2F, чёрная ~,2F, btc ~,2F @ ~A"
  102. (elt rates 1) (elt rates 2) (elt rates 3) (elt rates 4) (elt rates 5)
  103. (format-ts (local-time:unix-to-timestamp (elt rates 0)))))))