Module: SQA::FPOP

Defined in:
lib/sqa/fpop.rb

Class Method Summary collapse

Class Method Details

.build_interpretation(min_delta, max_delta) ⇒ String

Build human-readable interpretation string



136
137
138
139
140
141
142
# File 'lib/sqa/fpop.rb', line 136

def build_interpretation(min_delta, max_delta)
  direction = determine_direction(min_delta, max_delta)
  magnitude = calculate_magnitude(min_delta, max_delta)
  risk = (max_delta - min_delta).abs

  "#{direction}: #{magnitude.round(2)}% (±#{(risk / 2).round(2)}% risk)"
end

.calculate_magnitude(min_delta, max_delta) ⇒ Float

Calculate average expected movement (magnitude)



126
127
128
# File 'lib/sqa/fpop.rb', line 126

def calculate_magnitude(min_delta, max_delta)
  (min_delta + max_delta) / 2.0
end

.determine_direction(min_delta, max_delta) ⇒ Symbol

Determine directional bias from min/max deltas



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/sqa/fpop.rb', line 108

def determine_direction(min_delta, max_delta)
  if min_delta > 0 && max_delta > 0
    :UP
  elsif min_delta < 0 && max_delta < 0
    :DOWN
  elsif min_delta < 0 && max_delta > 0
    :UNCERTAIN
  else
    :FLAT
  end
end

.filter_by_quality(analysis, min_magnitude: nil, max_risk: nil, directions: [:UP, :DOWN, :UNCERTAIN, :FLAT]) ⇒ Array<Integer>

Filter FPL analysis results by criteria

Useful for finding high-quality trading opportunities

Examples:

Find low-risk bullish opportunities

analysis = SQA::FPOP.fpl_analysis(prices, fpop: 10)
indices = SQA::FPOP.filter_by_quality(
  analysis,
  min_magnitude: 5.0,
  max_risk: 10.0,
  directions: [:UP]
)


163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/sqa/fpop.rb', line 163

def filter_by_quality(analysis, min_magnitude: nil, max_risk: nil, directions: [:UP, :DOWN, :UNCERTAIN, :FLAT])
  indices = []

  analysis.each_with_index do |result, idx|
    next if min_magnitude && result[:magnitude] < min_magnitude
    next if max_risk && result[:risk] > max_risk
    next unless directions.include?(result[:direction])

    indices << idx
  end

  indices
end

.fpl(price, fpop: 14) ⇒ Array<Array<Float, Float>>

Calculate Future Period Loss/Profit for each point in price series

For each price point, looks ahead fpop periods and calculates:

  • Minimum percentage change (worst loss)

  • Maximum percentage change (best gain)

Examples:

SQA::FPOP.fpl([100, 105, 95, 110], fpop: 2)
# => [[-5.0, 5.0], [-9.52, 4.76], [15.79, 15.79]]


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/sqa/fpop.rb', line 44

def fpl(price, fpop: 14)
  validate_fpl_inputs(price, fpop)

  price_floats = price.map(&:to_f)
  result = []

  price_floats.each_with_index do |current_price, index|
    future_prices = price_floats[(index + 1)..(index + fpop)]
    break if future_prices.nil? || future_prices.empty?

    deltas = future_prices.map { |p| ((p - current_price) / current_price) * 100.0 }
    result << [deltas.min, deltas.max]
  end

  result
end

.fpl_analysis(price, fpop: 14) ⇒ Array<Hash>

Perform comprehensive FPL analysis with risk metrics and classification

Examples:

analysis = SQA::FPOP.fpl_analysis([100, 110, 120], fpop: 2)
analysis.first
# => {
#   min_delta: 10.0,
#   max_delta: 20.0,
#   risk: 10.0,
#   direction: :UP,
#   magnitude: 15.0,
#   interpretation: "UP: 15.0% (±5.0% risk)"
# }


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/sqa/fpop.rb', line 85

def fpl_analysis(price, fpop: 14)
  validate_fpl_inputs(price, fpop)

  fpl_results = fpl(price, fpop: fpop)

  fpl_results.map do |min_delta, max_delta|
    {
      min_delta: min_delta,
      max_delta: max_delta,
      risk: (max_delta - min_delta).abs,
      direction: determine_direction(min_delta, max_delta),
      magnitude: calculate_magnitude(min_delta, max_delta),
      interpretation: build_interpretation(min_delta, max_delta)
    }
  end
end

.risk_reward_ratios(analysis) ⇒ Array<Float>

Calculate risk-reward ratio for each analysis point



182
183
184
185
186
# File 'lib/sqa/fpop.rb', line 182

def risk_reward_ratios(analysis)
  analysis.map do |result|
    result[:risk] > 0 ? result[:magnitude].abs / result[:risk] : 0.0
  end
end