Module: Cotcube::Indicators

Defined in:
lib/cotcube-indicators.rb,
lib/cotcube-indicators/ema.rb,
lib/cotcube-indicators/rsi.rb,
lib/cotcube-indicators/sma.rb,
lib/cotcube-indicators/calc.rb,
lib/cotcube-indicators/index.rb,
lib/cotcube-indicators/score.rb,
lib/cotcube-indicators/change.rb,
lib/cotcube-indicators/__carrier.rb,
lib/cotcube-indicators/threshold.rb,
lib/cotcube-indicators/true_range.rb

Defined Under Namespace

Classes: Carrier

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.calcObject

.changeObject

.emaObject

.indexObject

.rsiObject

.scoreObject

.smaObject

.thresholdObject

.true_rangeObject

Instance Method Details

#calc(a:, b:, c: nil, d: nil, e: nil, f: nil, g: nil, h: nil, finalize: :to_f, &block) ⇒ Object

rubocop:disable Naming/MethodParameterName



5
6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/cotcube-indicators/calc.rb', line 5

def calc(a:, b:, c:nil, d:nil, e:nil, f:nil, g:nil, h:nil, finalize: :to_f, &block)   # rubocop:disable Naming/MethodParameterName
  lambda do |x|
    block.call(
      x[a.to_sym],
      (b.nil? ? nil : x[b.to_sym]),
      (c.nil? ? nil : x[c.to_sym]),
      (d.nil? ? nil : x[d.to_sym]),
      (e.nil? ? nil : x[e.to_sym]),
      (f.nil? ? nil : x[f.to_sym]),
      (g.nil? ? nil : x[g.to_sym]),
    ).send(finalize.to_sym)
  end
end

#change(key:, lookback: 1, debug: false) ⇒ Object

returns the difference between the current value and the first available value in carrier.

if block given, the block is evaluated as conditional, using current and carrier.get.last.
if evaluation is false, carrier remains untouched and 0 is returned


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
# File 'lib/cotcube-indicators/change.rb', line 8

def change(key:, lookback: 1, debug: false)
  unless lookback.is_a?(Integer) && (lookback >= 1)
    raise ArgumentError, 'invalid lookback period, need integer >= 1'
  end

  carrier = Cotcube::Indicators::Carrier.new(length: lookback + 1)

  lambda do |x|
    current = x[key.to_sym]
    current = 0 unless current.is_a?(Numeric)
    carrier << 0 if carrier.empty?
    if debug
      puts "comparing #{current} from #{key.to_sym} ... "\
           "#{lookback} ... #{carrier.inspect} ... yield"\
           " #{block_given? ? yield(carrier.get.last, current) : ''}"
    end
    if (not block_given?) || yield(carrier.get.last, current)  # rubocop:disable Style/GuardClause
      carrier << current
      ret = carrier.size < lookback + 1 ? 0 : (carrier.get.last - carrier.get.first)
      return ret.to_f
    else
      return 0
    end
  end
end

#ema(key:, length:, smoothing: nil) ⇒ Object

the classic exponential moving average

Raises:

  • (ArgumentError)


6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/cotcube-indicators/ema.rb', line 6

def ema(key:, length:, smoothing: nil)
  raise ArgumentError, 'Improper :length parameter, should be positive Integer' unless length.is_a? Integer and length.positive?

  smoothing ||= (2 / (length - 1).to_f.round(2))
  raise ArgumentError, 'Improper smoothing, should be Numeric' unless smoothing.is_a? Numeric

  carrier = Cotcube::Indicators::Carrier.new(length: length)
  lambda do |x|
    current = x[key.to_sym]
    carrier << if carrier.empty?
                 current * (smoothing / (1.0 + length))
               else
                 current * (smoothing / (1.0 + length)) + carrier.get[-1] * (1.0 - (smoothing / (1.0 + length)))
               end
    carrier.size < length ? -1.0 : carrier.get.last
  end
end

#index(key:, length:, debug: false, reverse: false, abs: false) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/cotcube-indicators/index.rb', line 5

def index(key:, length:, debug: false, reverse: false, abs: false)
  carrier = Carrier.new(length: length)

  lambda do |x|
    current = x[key.to_sym]
    if debug
      puts "comparing #{current} from #{key} ... "\
           "#{length.nil? ? 0 : length} with yield #{block_given? ? yield(current) : ''}"
    end
    if (not block_given?) || yield(current)
      current = current.abs if abs
      puts "CURRENT: #{current}" if debug
      carrier << current
      puts "CARRIER: #{carrier.inspect}" if debug
      divisor = carrier.get.max - carrier.get.min
      puts "#{divisor} = #{carrier.get.max} - #{carrier.get.min}" if debug
      return -1.0 if divisor.zero?

      res = ((current - carrier.get.min) / divisor.to_f)
      puts "RESULT: #{res}" if debug
      return reverse ? (1 - res.to_f).round(3).to_f : res.round(3).to_f
    else
      carrier << 0
      return 0
    end
  end
end

#rsi(length:, key:, debug: false) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/cotcube-indicators/rsi.rb', line 5

def rsi(length:, key:, debug: false)
  carrier = Carrier.new(length: length)

  lambda do |x|
    current = x[key.to_sym]
    carrier << current
    puts ". #{carrier.get} ." if debug
    return 50.0 if carrier.length < length

    u = carrier.get.select(&:positive?).map(&:abs).reduce(:+)
    d = carrier.get.select(&:negative?).map(&:abs).reduce(:+)
    d = d.nil? ? 10**-12 : d / length.to_f
    u = u.nil? ? 10**-12 : u / length.to_f
    return (100 - (100 / (1 + u / d))).to_f
  end
end

#score(key:, length:, abs: false, index: false) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/cotcube-indicators/score.rb', line 5

def score(key:, length:, abs: false, index: false)
  # returns the position of current within carrier after sorting, i.e. if current has the highest rank it becomes
  # 1, the lowest becomes :length to create comparable results for changing lengths, the score is put onto an
  # index with top rank of 1 (1 - 0/:length)
  carrier = Carrier.new(length: length)
  lambda do |x|
    current = x[key.to_sym]
    current = current.abs if abs
    if current.zero?
      carrier << 0
      return 0

    end
    carrier << current
    score = carrier.get.sort.reverse.find_index(current)
    score_index = (1 - score / length.to_f).round(3)
    index ? score_index : (score + 1)
  end
end

#sma(length:, key:) ⇒ Object



5
6
7
8
9
10
11
12
13
# File 'lib/cotcube-indicators/sma.rb', line 5

def sma(length:, key:)

  carrier = Cotcube::Indicators::Carrier.new(length: length)
  lambda do |x|
    current = x[key.to_sym]
    carrier << current
    carrier.size < length ? 0.0 : (carrier.get.reduce(:+) / carrier.length.to_f).to_f
  end
end

#threshold(key:, upper: nil, lower: nil, repeat: false, abs: false, debug: false) ⇒ Object

threshold returns

Raises:

  • (ArgumentError)


6
7
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
# File 'lib/cotcube-indicators/threshold.rb', line 6

def threshold(key:, upper: nil, lower: nil, repeat: false, abs: false, debug: false)
  raise ArgumentError, "Need to set :upper: (set to Float::INFINITY to omit)" if upper.nil? 
  raise ArgumentError, "Need to set :lower unless :abs is set" if lower.nil? and not abs
  raise ArgumentError, ":lower, upper must be numeric" if not upper.is_a?(Numeric) and ( abs ? true : lower.is_a?(Numeric) )
  raise ArgumentError, "Found bogus, :lower #{lower} must be lower than :upper #{upper}" if not abs and lower > upper
  # For this indicator, we just need to remembers -1, 0 or 1. That does not need a helper class.
  carrier = 0

  lambda do |current|
    current = current[key.to_sym]
    current = current.abs if abs
    puts "UPPER: #{upper}\tLOWER: #{lower}\tCARRIER was: #{carrier}\tCURRENT: #{current}" if debug
    # facing options
    # 1. carrier is zero
    if carrier.zero? 
      # 1.a. and threshold
      if current >= upper 
        carrier = 1
        return 1
      elsif not abs and current <= lower  
        carrier = -1
        return -1
      # 1.b. and no threshold
      else
        return 0
      end
    # 2. carrier is non-zero
    else
      # and threshold
      #   if threshold matches carrier, return 0 and keep carrier or return carrier if :repeat
      #   if not abs, allow reversing on appropriate threshold
      if carrier.positive? and current >= upper 
        return repeat ?  1 : 0 
      elsif not abs and carrier.negative? and current <= lower
        return repeat ? -1 : 0
      elsif not abs and carrier.positive? and current <= lower
        carrier = -1
        return -1
      elsif not abs and carrier.negative? and current >= upper
        carrier = 1
        return 1
      # and no threshold
      #   silently unset carrier, return 0
      else
        carrier = 0
        return 0 
      end
    end
  end
end

#true_range(high: :high, low: :low, close: :close) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/cotcube-indicators/true_range.rb', line 5

def true_range(high: :high, low: :low, close: :close)
  carrier = Carrier.new(length: 1)
  lambda do |current|
    raise "Missing keys of #{high}, #{low}, #{close} in '#{current}'" unless [high, low, close] - current.keys == []

    if carrier.empty?
      carrier << current
      return (current[high] - current[low]).to_f
    end
    last = carrier.get.first
    carrier << current
    ([current[high], last[close]].max - [current[low], last[close]].min).to_f
  end
end