[UP] add many more ion3 cfg -_-, powerfull, and add thinkpad xorg.conf/.Xmodmap,...
[arrow.git] / archlinux_conf / etc / ion3 / stock.lua
blobb4c731b409f21b61a97851ac4ede43f1e293aa9b
1 -- stock.lua
3 -- ABOUT
4 -- An Ion3 applet for retrieving and displaying stock market information
5 -- from http://finance.yahoo.com. You can set up a portfolio and monitor
6 -- its intraday performance.
9 -- QUICKSTART
10 -- 1. In template of you cfg_statusbar.lua insert: "%stock" (without quotes)
11 -- 2. Insert, in you cfg_ion.lua or run: dopath("stock")
12 -- 3. press MOD1+F10 to get the menu
13 -- 4. Add a ticket: e.g. "^N225" (without quotes) to monitor the Nikkei index.
16 -- COMMANDS
17 -- Here's the list of available commands:
18 -- - add-a-ticket: add a Yahoo ticket to monitor (e.g. "^N225" -
19 -- without quotes). You can also insert the quantity, separated by a
20 -- a comma: "TIT.MI,100" will insert 100 shares of TIT.MI in your portfolio.
21 -- - delete-a-ticket: remove a ticket
22 -- - suspend-updates: to stop retrieving data from Yahoo
23 -- - resume-updates: to resume retrieving data from Yahoo
24 -- - toggle-visibility: short or data display. You can configure
25 -- the string for the short display.
26 -- - update: force monitor to update data.
28 -- CONFIGURATION AND SETUP
29 -- You may configure this applet in cfg_statusbar.cfg
30 -- In mod_statusbar.launch_statusd{) insert something like this:
32 -- -- stock configuration options
33 -- stock = {
34 -- tickets = {"^N225", "^SPMIB", "TIT.MI"},
35 -- interval = 5 * 60 * 1000, -- check every 5 minutes
36 -- off_msg = "*Stock*", -- string to be displayed in "short" mode
37 -- susp_msg = "(Stock suspended)", -- string to be displayed when data
38 -- -- retrieval is suspended
39 -- susp_msg_hint = "critical", -- hint for suspended mode
40 -- unit = {
41 -- delta = "%",
42 -- },
43 -- important = {
44 -- delta = 0,
45 -- },
46 -- critical = {
47 -- delta = 0,
48 -- }
49 -- },
51 -- You can set "important" and "critical" thresholds for each meter.
53 -- PORTFOLIO
54 -- If you want to monitor a portfolio you can set it up in the configuration with
55 -- something like this:
56 -- stock = {
57 -- off_msg = "*MyStock",
58 -- portfolio = {
59 -- ["TIT.MI"] = 2000,
60 -- ["IBZL.MI"] = 1500,
61 -- },
62 -- },
63 -- where numbers represent the quantities of shares you posses. When
64 -- visibility will be set to OFF, in the statusbar (if you use ONLY the
65 -- %stock meter) you will get a string ("MyStock" in the above example)
66 -- red or green depending on the its global performance.
68 -- FEEDBACK
69 -- Please report your feedback, bugs reports, features request, to the
70 -- above email address.
72 -- REVISIONS
73 -- 2006-07-10 first release
75 -- LEGAL
76 -- Copyright (C) 2006 Andrea Rossato
78 -- This program is free software; you can redistribute it and/or
79 -- modify it under the terms of the GNU General Public License
80 -- as published by the Free Software Foundation; either version 2
81 -- of the License, or (at your option) any later version.
83 -- This software is distributed in the hope that it will be useful,
84 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
85 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
86 -- GNU General Public License for more details.
88 -- You should have received a copy of the GNU General Public License
89 -- along with this program; if not, write to the Free Software
90 -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
93 -- Have fun!
95 -- Andrea Rossato arossato AT istitutocolli DOT org
98 -- key bindings
99 -- you can (should) change the key bindings to your liking
100 defbindings("WMPlex", {
101 kpress(MOD1.."F10", "mod_query.query_menu(_, 'stockmenu', 'StockMonitor Menu: ')"),
102 kpress(MOD1.."Shift+F10", "StockMonitor.add_ticket(_)"),
104 defmenu("stockmenu", {
105 menuentry("update now", "StockMonitor.update()"),
106 menuentry("add a ticket", "StockMonitor.add_ticket(_)"),
107 menuentry("delete a Ticket", "StockMonitor.del_ticket(_)"),
108 menuentry("toggle visibility", "StockMonitor.toggle()"),
109 menuentry("suspend updates", "StockMonitor.suspend()"),
110 menuentry("resume updates", "StockMonitor.resume()"),
114 -- our stuff
115 local function new_stock()
116 local this = {
118 -- configuration
120 config = {
121 tickets = {},
122 interval = 5 * 60 * 1000, -- check every 5 minutes
123 off_msg = "*Stock*",
124 susp_msg = "(Stock suspended)",
125 susp_msg_hint = "critical",
126 toggle = "on",
127 portfolio = {},
128 unit = {
129 delta = "%",
131 important = {
132 delta = 0,
134 critical = {
135 delta = 0,
138 -- end configuration
140 status_timer = ioncore.create_timer(),
141 url = "http://finance.yahoo.com/d/quotes.csv",
142 data = { },
143 backup = { },
144 paths = ioncore.get_paths(),
147 -- some needed global functions
148 function table.merge(t1, t2)
149 local t=table.copy(t1, false)
150 for k, v in pairs(t2) do
151 t[k]=v
153 return t
156 function math.dpr(number, signs_q)
157 local pattern = "%d+"
158 if signs_q == nil then
159 signs_q = 2
161 if signs_q ~= 0 then
162 pattern = pattern.."%."
164 for i = 1, tonumber (signs_q) do
165 pattern = pattern.."%d"
167 return string.gsub (number, "("..pattern..")(.*)", "%1")
170 -- gets configuration values and store them in this.config (public)
171 function this.process_config()
172 this.get_sb()
173 local c = ioncore.read_savefile("cfg_statusd")
174 if c.stock then
175 this.config = table.merge(this.config, c.stock)
177 c = ioncore.read_savefile("cfg_stock")
178 if c then
179 this.config.portfolio = table.merge(this.config.portfolio, c)
181 this.process_portfolio()
184 -- gets tickets from portfolio
185 function this.process_portfolio()
186 for t,q in pairs(this.config.portfolio) do
187 if t then table.insert(this.config.tickets, t) end
191 -- gets the statusbar obj and makes a backup of the sb table (just in case)
192 function this.get_sb()
193 for _, sb in pairs(mod_statusbar.statusbars()) do
194 this.StatusBar = sb
196 ioncore.write_savefile("stock_sb", this.StatusBar:get_template_table())
199 -- removes a meter from the statusbar and inserts a new template chunk
200 function this.sb_insert_tmpl_chunk(chunk, meter)
201 local pos, old_sb_chunk
202 this.restore_sb(meter)
203 local old_sb = this.StatusBar:get_template_table()
204 for a, item in pairs(old_sb) do
205 if item.meter == meter then
206 pos = a
207 old_sb_chunk = item
208 break
211 this.backup[meter] = old_sb_chunk
212 mod_statusbar.inform("start_insert_by_"..meter, "")
213 mod_statusbar.inform("end_insert_by_"..meter, "")
214 local new_sb_chunk = mod_statusbar.template_to_table("%start_insert_by_"..meter.." "..
215 chunk.." %end_insert_by_"..meter)
216 local new_sb = old_sb
217 table.remove(new_sb, pos)
218 for i,v in pairs(new_sb_chunk) do
219 table.insert(new_sb, pos, v)
220 pos = pos + 1
222 this.StatusBar:set_template_table(new_sb)
225 -- restores the statusbar with the original meter
226 function this.restore_sb(meter)
227 local st, en
228 local old_sb = this.StatusBar:get_template_table()
229 for a, item in pairs(old_sb) do
230 if item.meter == "start_insert_by_"..meter then
231 st = a
233 if item.meter == "end_insert_by_"..meter then
234 en = a
235 break
238 local new_sb = old_sb
239 if en then
240 for a=st, en, 1 do
241 table.remove(new_sb, st)
243 table.insert(new_sb, st, this.backup[meter])
244 this.StatusBar:set_template_table(new_sb)
245 mod_statusbar.inform(meter, "")
249 -- gets ticket's data
250 function this.get_record(t)
251 local command = "wget -O "..this.paths.sessiondir.."/"..
252 string.gsub(t,"%^" ,"")..".cvs "..this.url.."?s="..
253 string.gsub(t,"%^" ,"%%5E")..
254 "\\&f=sl1d1t1c1ohgv\\&e=.csv"
255 os.execute(command)
256 local f = io.open(this.paths.sessiondir .."/"..string.gsub(t,"%^" ,"")..".cvs", "r")
257 if not f then return end
258 local s=f:read("*all")
259 f:close()
260 os.execute("rm "..this.paths.sessiondir .."/"..string.gsub(t,"%^" ,"")..".cvs")
261 return s
264 -- parses ticket's data and store them in this.data.ticketname
265 function this.process_record(s)
266 local _,_,t = string.find(s, '"%^?(.-)".*' )
267 t = string.gsub(t , "%.", "")
268 this.data[t] = { raw_data = s, }
269 _, _,
270 this.data[t].ticket,
271 this.data[t].quote,
272 this.data[t].date,
273 this.data[t].time,
274 this.data[t].difference,
275 this.data[t].open,
276 this.data[t].high,
277 this.data[t].low,
278 this.data[t].volume =
279 string.find(s, '"(.-)",(.-),"(.-)","(.-)",(.-),(.-),(.-),(.-),(.-)' )
280 if tonumber(this.data[t].difference) ~= nil then
281 this.data[t].delta = math.dpr((this.data[t].difference /
282 (this.data[t].quote - this.data[t].difference) * 100), 2)
283 else
284 this.data[t] = nil
288 -- updates tickets' data
289 function this.update_data()
290 for _, v in pairs(this.config.tickets) do
291 this.process_record(this.get_record(v))
295 -- gets threshold info
296 function this.get_hint(meter, val)
297 local hint = "normal"
298 local crit = this.config.critical[meter]
299 local imp = this.config.important[meter]
300 if crit and tonumber(val) < crit then
301 hint = "critical"
302 elseif imp and tonumber(val) >= imp then
303 hint = "important"
305 return hint
308 -- gets the unit (if any) of each meter
309 function this.get_unit(meter)
310 local unit = this.config.unit[meter]
311 if unit then return unit end
312 return ""
315 -- gets the quantity (if any) of each ticket
316 function this.get_quantity(t)
317 local quantity = this.config.portfolio[t]
318 if quantity then return quantity end
319 return 1
322 -- notifies data to statusbar
323 function this.notify()
324 local newtmpl = ""
325 local perf = 0
326 local base = 0
327 for i,v in pairs(this.data) do
328 newtmpl = newtmpl..v.ticket..": %stock_delta_"..i.." "
329 perf = perf + this.get_quantity(v.ticket) + (v.delta * this.get_quantity(v.ticket) / 100)
330 base = base + (this.get_quantity(v.ticket))
331 for ii,vv in pairs(this.data[i]) do
332 mod_statusbar.inform("stock_"..ii.."_"..i.."_hint", this.get_hint(ii, vv))
333 mod_statusbar.inform("stock_"..ii.."_"..i, vv..this.get_unit(ii))
336 if this.config.toggle == "off" then
337 if perf > base then
338 mod_statusbar.inform("stock",this.config.off_msg )
339 mod_statusbar.inform("stock_hint", "important")
340 else
341 mod_statusbar.inform("stock",this.config.off_msg )
342 mod_statusbar.inform("stock_hint", "critical")
344 else
345 this.sb_insert_tmpl_chunk(newtmpl, "stock")
347 mod_statusbar.update()
350 -- checks if the timer is set and ther restarts
351 function this.restart()
352 if not this.status_timer then this.resume() end
353 this.loop()
356 -- main loop
357 function this.loop()
358 if this.status_timer ~= nil and mod_statusbar ~= nil then
359 this.update_data()
360 this.notify()
361 this.status_timer:set(this.config.interval, this.loop)
365 -- public methods
367 function this.update()
368 this.loop()
371 function this.add_ticket(mplex)
372 local handler = function(mplex, str)
373 local _,_,t,_,q = string.find(str, "(.*)(,)(%d*)")
374 ioncore.write_savefile("debug", { ["q"] = q, ["t"] = t})
375 if q then this.config.portfolio[t] = tonumber(q)
376 else this.config.portfolio[str] = 1 end
377 ioncore.write_savefile("cfg_stock", this.config.portfolio)
378 this.process_portfolio()
379 this.restart()
381 mod_query.query(mplex, TR("Add a ticket (format: ticketname - e.g. ^N225 or tickename,quantity e.g: ^N225,100):"),
382 nil, handler, nil, "stock")
385 function this.del_ticket(mplex)
386 local handler = function(mplex, str)
387 for i,v in pairs(this.config.tickets) do
388 if this.config.tickets[i] == str then
389 this.config.tickets[i] = nil
390 break
393 this.config.portfolio[str] = nil
394 ioncore.write_savefile("cfg_stock", this.config.portfolio)
395 this.data[string.gsub(str,"[%^%.]" ,"")] = nil
396 this.restart()
398 mod_query.query(mplex, TR("Delete a ticket (format: tickename e.g. ^N225):"), nil, handler,
399 nil, "stock")
402 function this.suspend()
403 this.restore_sb("stock")
404 mod_statusbar.inform("stock",this.config.susp_msg )
405 mod_statusbar.inform("stock_hint", this.config.susp_msg_hint)
406 mod_statusbar.update()
407 this.status_timer = nil
410 function this.resume()
411 this.status_timer = ioncore.create_timer()
412 this.restart()
415 function this.toggle()
416 if this.config.toggle == "on" then
417 this.config.toggle = "off"
418 this.restore_sb("stock")
419 mod_statusbar.inform("stock",this.config.off_msg )
420 else
421 this.config.toggle = "on"
423 this.restart()
426 -- constructor
427 function this.init()
428 this.process_config()
429 this.loop()
432 this.init()
433 -- return this
434 return {
435 update = this.update,
436 add_ticket = this.add_ticket,
437 del_ticket = this.del_ticket,
438 toggle = this.toggle,
439 suspend = this.suspend,
440 resume = this.resume,
441 data = this.data,
442 config = this.config,
446 -- there we go!
447 StockMonitor = new_stock()