Module: RTicker

Defined in:
lib/rticker/net.rb,
lib/rticker/tput.rb,
lib/rticker/entry.rb,
lib/rticker/equity.rb,
lib/rticker/future.rb,
lib/rticker/option.rb,
lib/rticker/parser.rb,
lib/rticker/options.rb,
lib/rticker/printer.rb,
lib/rticker/currency.rb,
lib/rticker/separator.rb,
lib/rticker/application.rb

Defined Under Namespace

Classes: Application, Currency, Entry, Equity, Future, Net, Option, Options, Separator

Constant Summary collapse

DEFAULT_ENTRY_FILE =
"#{ENV['HOME']}/.rticker"

Class Method Summary collapse

Class Method Details

.parse_entry(entry) ⇒ Object

Parse a raw text entry into an instance of Equity, Future, Currency, Option, or Separator.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/rticker/parser.rb', line 53

def RTicker.parse_entry (entry)

  # An entry starting with a dash represents a Separator
  return Separator.new if entry[0,1] == "-"

  # An entry beginning with an asterisk represents a bold entry.
  bold = false
  if entry[0,1] == "*"
    bold = true
    entry = entry[1..-1] # Shift off asterisk
  end
  
  ## Here is a breakdown of the format: 
  ## [sign]symbol[,desc[,purchase_count@purchase_price]]
  ## Where sign can be a "#" (Future), "!" (Option), "^" (Yahoo Equity), 
  ## or "$" (Currency).  Anything else represents a Google Equity.
  pattern = /^([#!^$])?([^,]+)(?:,([^,]*)(?:,(-?[0-9]+)@([0-9]+\.?[0-9]*))?)?/
  match = pattern.match entry

  sign = match[1]
  symbol = match[2]
  # Because description is optional, let's make it clear with a nil that no
  # description was provided.
  description = match[3] == "" ? nil : match[3]
  purchase_count = match[4]
  purchase_price = match[5]

  args = [symbol, description, purchase_count.to_f, purchase_price.to_f, bold]
  case sign
  when "#"
    # An entry starting with a hash is a Future.  A useful mnemonic is to
    # think of the "pound" sign and think of lbs of a commodity.
    return Future.new(*args)
  when "!"
    # An entry starting with an exclamation mark is an option contract.
    return Option.new(*args)
  when "^"
    # An entry starting with a caret represents an equity to be fetched
    # from Yahoo Finance.
    e = Equity.new(*args)
    e.source = :yahoo
    return e
  when "$"
    # An entry starting with a dollar sign represents a currency pair.
    return Currency.new(*args)
  else
    # Everthing else is an equity to be fetched from Google Finance (the
    # default).
    return Equity.new(*args)
  end
end

.read_entry_file(file, context = nil) ⇒ Object

Read raw text entries, line by line, from file. Return result as an array of text entries. File “-” represents STDIN.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/rticker/parser.rb', line 9

def RTicker.read_entry_file (file, context=nil)
  context ||= Dir.pwd

  # If this is a relative path, make it absolute according to context
  if file != "-" and file[0,1] != "/"
    file = [context, file].join("/")
  end

  # If we're not dealing with a readable source then get out of here
  return [] if file != "-" and not File.readable? file

  if File.symlink? file
    # Follow symlinks so that we can honor relative include directives
    return read_entry_file(Pathname.new(file).realpath.to_s)
  end
  
  entries = []
  source = if file == "-" then $stdin else File.open(file) end
  source.each do |line|
    line.chomp!
    case line
    when  /^;/, /^\s*$/
      # If this line starts with a semicolon (comment) or only contains
      # whitespace, ignore it.
      next
    when /^@.+/
      # If this line starts with an @ sign, then this is an 'include'
      # directive.  Ie, this entry is simply including another entry file.
      # Eg: @otherfile.txt
      # This will include another file called otherfile.txt
      include_file = line[1..-1]
      context = File.dirname(file) unless file == "-"
      entries += read_entry_file(include_file, context)
    else
      entries << line
    end
  end
  
  entries
end

.tput(args) ⇒ Object



7
8
9
10
11
12
13
# File 'lib/rticker/tput.rb', line 7

def RTicker.tput (args)
  if $__tput_cache.has_key? args
    $__tput_cache[args]
  else
    $__tput_cache[args] = %x[tput #{args}]
  end
end

.update_screen(entries, no_color = false, once = false) ⇒ Object

Update the screen with the latest information.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/rticker/printer.rb', line 8

def RTicker.update_screen (entries, no_color=false, once=false)
  # An interesting side-effect of using unless here is that if no_color is
  # true, then all of these variables are still created, but set to nil.
  # So setting no_color effectively turns all of the following variables
  # into empty strings, turning off terminal manipulation.
  hide_cursor = tput "civis" unless no_color
  show_cursor = tput "cnorm" unless no_color
  bold = tput "bold" unless no_color
  unbold = tput "sgr0" unless no_color
  default = tput "sgr0" unless no_color
  red = tput "setaf 1" unless no_color
  green = tput "setaf 2" unless no_color
  blue = tput "setaf 4" unless no_color

  if not once
    print %x[clear]
    print "#{hide_cursor}"
  end
  print "#{default}"
  
  # Calculate the longest symbol name or description
  max_length = 0
  for entry in entries.select {|e| not e.is_a? Separator}
    output = entry.description || entry.symbol
    max_length = [output.size, max_length].max
  end
  max_length += 2 # Give a little extra breathing room

  for entry in entries
    if entry.is_a? Separator
      print "___________________________________________\n"
      next # Skip to the next entry
    end

    # Prefer a description over a raw symbol name.  Use max_length to left
    # align the output with extra padding on the right.  This lines
    # everything up nice and neat.
    output = "%-#{max_length}s" % (entry.description || entry.symbol)
    output = "#{bold}#{output}#{unbold}" if entry.bold?

    # If we're still waiting for valid responses from yahoo or google, then
    # just let the user know that they need to wait.
    curr_value = entry.curr_value || "please wait..."

    if entry.is_a? Option
      curr_value = "#{entry.bid}/#{entry.ask}" if entry.bid
    end

    # Does this entry have a start_value?  If so, let's calculate the
    # change in percent.
    change_string = nil
    if entry.respond_to? :start_value and not entry.start_value.nil?
      change = entry.curr_value - entry.start_value
      change_percent = (change / entry.start_value) * 100
      color = (change >= 0 ? "#{green}" : "#{red}")
      change_string = " (#{color}%+.2f %%%0.2f#{default})" % [change, change_percent]
    end

    # If this entry has purchase info, let's calculate profit/loss
    profit_string = nil
    if entry.purchase_count != 0 and not entry.curr_value.nil?
      count = entry.purchase_count
      price = entry.purchase_price
      # Options are purchased in multiples of 100 of the contract price
      count *= 100 if entry.is_a? Option 
      # There is also a price multiplier for futures, but they are
      # completely different for each contract.  So the user will simply
      # need to enter the correct multiplier when configuring the
      # purchase_count in the ticker entry.  For instance, a contract for
      # CL, crude light sweet oil, has a multiplier of 1000.  Ie, one
      # contract represents 1000 barrels of oil.  So if a contract is
      # bought, the user should enter a purchase_count of 1000.  If the
      # contract is sold (a short position), the purchase_count should be
      # -1000.
      profit_loss = count * entry.curr_value - count * price
      profit_loss_percent = profit_loss / (count*price) * 100
      color = (profit_loss >= 0 ? "#{green}" : "#{red}")
      profit_string = " = #{color}$%.2f %%%0.2f#{default}" % [profit_loss, profit_loss_percent]
    end

    case entry
    when Equity
      print "#{output} #{curr_value}#{change_string}#{profit_string}"
    when Future
      print "#{output} #{curr_value}#{change_string}#{profit_string}"
    when Option
      print "#{output} #{curr_value}#{profit_string}"
    when Currency
      print "#{output} #{curr_value}#{profit_string}"
    end

    # Let the user know with a blue asterisk if this entry has changed
    # within the past 5 minutes.
    if entry.last_changed and (Time.now() - entry.last_changed) <= 5*60
      print " #{blue}*#{default}"
    end

    print "\n"
  end # for
  
end