sqa 0.0.24 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.goose/memory/development.txt +3 -0
- data/.semver +6 -0
- data/ARCHITECTURE.md +648 -0
- data/CHANGELOG.md +82 -0
- data/CLAUDE.md +653 -0
- data/COMMITS.md +196 -0
- data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
- data/NEXT-STEPS.md +154 -0
- data/README.md +812 -262
- data/TASKS.md +358 -0
- data/TEST_RESULTS.md +140 -0
- data/TODO.md +42 -0
- data/_notes.txt +25 -0
- data/bin/sqa-console +11 -0
- data/data/talk_talk.json +103284 -0
- data/develop_summary.md +313 -0
- data/docs/advanced/backtesting.md +206 -0
- data/docs/advanced/ensemble.md +68 -0
- data/docs/advanced/fpop.md +153 -0
- data/docs/advanced/index.md +112 -0
- data/docs/advanced/multi-timeframe.md +67 -0
- data/docs/advanced/pattern-matcher.md +75 -0
- data/docs/advanced/portfolio-optimizer.md +79 -0
- data/docs/advanced/portfolio.md +166 -0
- data/docs/advanced/risk-management.md +210 -0
- data/docs/advanced/strategy-generator.md +158 -0
- data/docs/advanced/streaming.md +209 -0
- data/docs/ai_and_ml.md +80 -0
- data/docs/api/dataframe.md +1115 -0
- data/docs/api/index.md +126 -0
- data/docs/assets/css/custom.css +88 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/concepts/index.md +68 -0
- data/docs/contributing/index.md +60 -0
- data/docs/data-sources/index.md +66 -0
- data/docs/data_frame.md +317 -97
- data/docs/factors_that_impact_price.md +26 -0
- data/docs/finviz.md +11 -0
- data/docs/fx_pro_bit.md +25 -0
- data/docs/genetic_programming.md +104 -0
- data/docs/getting-started/index.md +123 -0
- data/docs/getting-started/installation.md +229 -0
- data/docs/getting-started/quick-start.md +244 -0
- data/docs/i_gotta_an_idea.md +22 -0
- data/docs/index.md +163 -0
- data/docs/indicators/index.md +97 -0
- data/docs/indicators.md +110 -24
- data/docs/options.md +8 -0
- data/docs/strategies/bollinger-bands.md +146 -0
- data/docs/strategies/consensus.md +64 -0
- data/docs/strategies/custom.md +310 -0
- data/docs/strategies/ema.md +53 -0
- data/docs/strategies/index.md +92 -0
- data/docs/strategies/kbs.md +164 -0
- data/docs/strategies/macd.md +96 -0
- data/docs/strategies/market-profile.md +54 -0
- data/docs/strategies/mean-reversion.md +58 -0
- data/docs/strategies/rsi.md +95 -0
- data/docs/strategies/sma.md +55 -0
- data/docs/strategies/stochastic.md +63 -0
- data/docs/strategies/volume-breakout.md +54 -0
- data/docs/tags.md +7 -0
- data/docs/true_strength_index.md +46 -0
- data/docs/weighted_moving_average.md +48 -0
- data/examples/README.md +354 -0
- data/examples/advanced_features_example.rb +350 -0
- data/examples/fpop_analysis_example.rb +191 -0
- data/examples/genetic_programming_example.rb +148 -0
- data/examples/kbs_strategy_example.rb +208 -0
- data/examples/pattern_context_example.rb +300 -0
- data/examples/rails_app/Gemfile +34 -0
- data/examples/rails_app/README.md +416 -0
- data/examples/rails_app/app/assets/javascripts/application.js +107 -0
- data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
- data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
- data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
- data/examples/rails_app/app/controllers/application_controller.rb +22 -0
- data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
- data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
- data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
- data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
- data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
- data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
- data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
- data/examples/rails_app/app/views/errors/show.html.erb +17 -0
- data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
- data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
- data/examples/rails_app/bin/rails +6 -0
- data/examples/rails_app/config/application.rb +45 -0
- data/examples/rails_app/config/boot.rb +5 -0
- data/examples/rails_app/config/database.yml +18 -0
- data/examples/rails_app/config/environment.rb +11 -0
- data/examples/rails_app/config/routes.rb +26 -0
- data/examples/rails_app/config.ru +8 -0
- data/examples/realtime_stream_example.rb +274 -0
- data/examples/sinatra_app/Gemfile +22 -0
- data/examples/sinatra_app/QUICKSTART.md +159 -0
- data/examples/sinatra_app/README.md +461 -0
- data/examples/sinatra_app/app.rb +344 -0
- data/examples/sinatra_app/config.ru +5 -0
- data/examples/sinatra_app/public/css/style.css +659 -0
- data/examples/sinatra_app/public/js/app.js +107 -0
- data/examples/sinatra_app/views/analyze.erb +306 -0
- data/examples/sinatra_app/views/backtest.erb +325 -0
- data/examples/sinatra_app/views/dashboard.erb +419 -0
- data/examples/sinatra_app/views/error.erb +58 -0
- data/examples/sinatra_app/views/index.erb +118 -0
- data/examples/sinatra_app/views/layout.erb +61 -0
- data/examples/sinatra_app/views/portfolio.erb +43 -0
- data/examples/strategy_generator_example.rb +346 -0
- data/hsa_portfolio.csv +11 -0
- data/justfile +0 -0
- data/lib/api/alpha_vantage_api.rb +462 -0
- data/lib/sqa/backtest.rb +329 -0
- data/lib/sqa/data_frame/alpha_vantage.rb +43 -65
- data/lib/sqa/data_frame/data.rb +92 -0
- data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
- data/lib/sqa/data_frame.rb +148 -243
- data/lib/sqa/ensemble.rb +359 -0
- data/lib/sqa/fpop.rb +199 -0
- data/lib/sqa/gp.rb +259 -0
- data/lib/sqa/indicator.rb +5 -8
- data/lib/sqa/init.rb +15 -8
- data/lib/sqa/market_regime.rb +240 -0
- data/lib/sqa/multi_timeframe.rb +379 -0
- data/lib/sqa/pattern_matcher.rb +497 -0
- data/lib/sqa/portfolio.rb +260 -6
- data/lib/sqa/portfolio_optimizer.rb +377 -0
- data/lib/sqa/risk_manager.rb +442 -0
- data/lib/sqa/seasonal_analyzer.rb +209 -0
- data/lib/sqa/sector_analyzer.rb +300 -0
- data/lib/sqa/stock.rb +67 -125
- data/lib/sqa/strategy/bollinger_bands.rb +42 -0
- data/lib/sqa/strategy/consensus.rb +5 -2
- data/lib/sqa/strategy/kbs_strategy.rb +470 -0
- data/lib/sqa/strategy/macd.rb +46 -0
- data/lib/sqa/strategy/mp.rb +1 -1
- data/lib/sqa/strategy/stochastic.rb +60 -0
- data/lib/sqa/strategy/volume_breakout.rb +57 -0
- data/lib/sqa/strategy.rb +5 -0
- data/lib/sqa/strategy_generator.rb +947 -0
- data/lib/sqa/stream.rb +361 -0
- data/lib/sqa/version.rb +1 -7
- data/lib/sqa.rb +23 -16
- data/main.just +81 -0
- data/mkdocs.yml +288 -0
- data/trace.log +0 -0
- metadata +261 -51
- data/bin/sqa +0 -6
- data/lib/patches/dry-cli.rb +0 -228
- data/lib/sqa/activity.rb +0 -10
- data/lib/sqa/cli.rb +0 -62
- data/lib/sqa/commands/analysis.rb +0 -309
- data/lib/sqa/commands/base.rb +0 -139
- data/lib/sqa/commands/web.rb +0 -199
- data/lib/sqa/commands.rb +0 -22
- data/lib/sqa/constants.rb +0 -23
- data/lib/sqa/indicator/average_true_range.rb +0 -33
- data/lib/sqa/indicator/bollinger_bands.rb +0 -28
- data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
- data/lib/sqa/indicator/donchian_channel.rb +0 -29
- data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
- data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
- data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
- data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
- data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
- data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
- data/lib/sqa/indicator/market_profile.rb +0 -32
- data/lib/sqa/indicator/mean_reversion.rb +0 -37
- data/lib/sqa/indicator/momentum.rb +0 -28
- data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
- data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
- data/lib/sqa/indicator/predict_next_value.rb +0 -202
- data/lib/sqa/indicator/relative_strength_index.rb +0 -47
- data/lib/sqa/indicator/simple_moving_average.rb +0 -24
- data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
- data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
- data/lib/sqa/indicator/true_range.rb +0 -39
- data/lib/sqa/trade.rb +0 -26
data/lib/sqa/stream.rb
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thread'
|
|
4
|
+
|
|
5
|
+
=begin
|
|
6
|
+
|
|
7
|
+
Real-Time Stock Price Stream Processor
|
|
8
|
+
|
|
9
|
+
This module provides real-time processing of stock price updates,
|
|
10
|
+
calculating indicators on-the-fly and generating trading signals
|
|
11
|
+
as new market data arrives.
|
|
12
|
+
|
|
13
|
+
Key Features:
|
|
14
|
+
- Event-driven architecture for live data processing
|
|
15
|
+
- Rolling window of recent prices and indicators
|
|
16
|
+
- On-demand indicator calculation
|
|
17
|
+
- Strategy execution on price updates
|
|
18
|
+
- Thread-safe operations
|
|
19
|
+
- Callback system for signal notifications
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
stream = SQA::Stream.new(
|
|
23
|
+
ticker: 'AAPL',
|
|
24
|
+
window_size: 100,
|
|
25
|
+
strategies: [SQA::Strategy::RSI, SQA::Strategy::MACD]
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Add callback for signals
|
|
29
|
+
stream.on_signal do |signal, data|
|
|
30
|
+
puts "Signal: #{signal} at price #{data[:price]}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Feed live price data
|
|
34
|
+
stream.update(price: 150.25, volume: 1_000_000, timestamp: Time.now)
|
|
35
|
+
|
|
36
|
+
=end
|
|
37
|
+
|
|
38
|
+
module SQA
|
|
39
|
+
class Stream
|
|
40
|
+
attr_reader :ticker, :window_size, :strategies, :data_buffer, :indicator_cache
|
|
41
|
+
|
|
42
|
+
def initialize(ticker:, window_size: 100, strategies: [])
|
|
43
|
+
@ticker = ticker
|
|
44
|
+
@window_size = window_size
|
|
45
|
+
@strategies = Array(strategies)
|
|
46
|
+
@signal_callbacks = []
|
|
47
|
+
@update_callbacks = []
|
|
48
|
+
|
|
49
|
+
# Thread-safe data structures
|
|
50
|
+
@mutex = Mutex.new
|
|
51
|
+
@data_buffer = {
|
|
52
|
+
prices: [],
|
|
53
|
+
volumes: [],
|
|
54
|
+
highs: [],
|
|
55
|
+
lows: [],
|
|
56
|
+
timestamps: []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@indicator_cache = {}
|
|
60
|
+
@last_signal = :hold
|
|
61
|
+
@update_count = 0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Add a strategy to the stream processor
|
|
65
|
+
def add_strategy(strategy)
|
|
66
|
+
@mutex.synchronize do
|
|
67
|
+
@strategies << strategy
|
|
68
|
+
end
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Register callback for trading signals
|
|
73
|
+
#
|
|
74
|
+
# Example:
|
|
75
|
+
# stream.on_signal do |signal, data|
|
|
76
|
+
# puts "#{signal.upcase} at $#{data[:price]}"
|
|
77
|
+
# end
|
|
78
|
+
def on_signal(&block)
|
|
79
|
+
@signal_callbacks << block
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Register callback for price updates
|
|
84
|
+
#
|
|
85
|
+
# Example:
|
|
86
|
+
# stream.on_update do |data|
|
|
87
|
+
# puts "Price updated: $#{data[:price]}"
|
|
88
|
+
# end
|
|
89
|
+
def on_update(&block)
|
|
90
|
+
@update_callbacks << block
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Update stream with new market data
|
|
95
|
+
#
|
|
96
|
+
# Required fields: price
|
|
97
|
+
# Optional fields: volume, high, low, timestamp
|
|
98
|
+
def update(price:, volume: nil, high: nil, low: nil, timestamp: Time.now)
|
|
99
|
+
@mutex.synchronize do
|
|
100
|
+
# Add to buffers
|
|
101
|
+
@data_buffer[:prices] << price.to_f
|
|
102
|
+
@data_buffer[:volumes] << (volume || 0).to_f
|
|
103
|
+
@data_buffer[:highs] << (high || price).to_f
|
|
104
|
+
@data_buffer[:lows] << (low || price).to_f
|
|
105
|
+
@data_buffer[:timestamps] << timestamp
|
|
106
|
+
|
|
107
|
+
# Trim buffers to window size
|
|
108
|
+
trim_buffers
|
|
109
|
+
|
|
110
|
+
# Clear indicator cache (will be recalculated on demand)
|
|
111
|
+
@indicator_cache.clear
|
|
112
|
+
|
|
113
|
+
@update_count += 1
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Prepare update data
|
|
117
|
+
update_data = {
|
|
118
|
+
price: price,
|
|
119
|
+
volume: volume,
|
|
120
|
+
high: high,
|
|
121
|
+
low: low,
|
|
122
|
+
timestamp: timestamp,
|
|
123
|
+
count: @update_count
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Notify update callbacks
|
|
127
|
+
@update_callbacks.each { |callback| callback.call(update_data) }
|
|
128
|
+
|
|
129
|
+
# Process strategies if we have enough data
|
|
130
|
+
process_strategies if sufficient_data?
|
|
131
|
+
|
|
132
|
+
true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Get current price
|
|
136
|
+
def current_price
|
|
137
|
+
@mutex.synchronize { @data_buffer[:prices].last }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Get recent prices
|
|
141
|
+
def recent_prices(count = nil)
|
|
142
|
+
@mutex.synchronize do
|
|
143
|
+
count ? @data_buffer[:prices].last(count) : @data_buffer[:prices].dup
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Get recent volumes
|
|
148
|
+
def recent_volumes(count = nil)
|
|
149
|
+
@mutex.synchronize do
|
|
150
|
+
count ? @data_buffer[:volumes].last(count) : @data_buffer[:volumes].dup
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Calculate or retrieve cached indicator
|
|
155
|
+
#
|
|
156
|
+
# Example:
|
|
157
|
+
# rsi = stream.indicator(:rsi, period: 14)
|
|
158
|
+
# sma = stream.indicator(:sma, period: 20)
|
|
159
|
+
def indicator(name, **options)
|
|
160
|
+
cache_key = [name, options].hash
|
|
161
|
+
|
|
162
|
+
@mutex.synchronize do
|
|
163
|
+
return @indicator_cache[cache_key] if @indicator_cache.key?(cache_key)
|
|
164
|
+
|
|
165
|
+
prices = @data_buffer[:prices].dup
|
|
166
|
+
volumes = @data_buffer[:volumes].dup
|
|
167
|
+
highs = @data_buffer[:highs].dup
|
|
168
|
+
lows = @data_buffer[:lows].dup
|
|
169
|
+
|
|
170
|
+
result = calculate_indicator(name, prices, volumes, highs, lows, **options)
|
|
171
|
+
@indicator_cache[cache_key] = result
|
|
172
|
+
result
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Get current trading signal from last strategy execution
|
|
177
|
+
def current_signal
|
|
178
|
+
@last_signal
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Get statistics about the stream
|
|
182
|
+
def stats
|
|
183
|
+
@mutex.synchronize do
|
|
184
|
+
{
|
|
185
|
+
ticker: @ticker,
|
|
186
|
+
updates: @update_count,
|
|
187
|
+
buffer_size: @data_buffer[:prices].size,
|
|
188
|
+
window_size: @window_size,
|
|
189
|
+
current_price: @data_buffer[:prices].last,
|
|
190
|
+
price_range: price_range,
|
|
191
|
+
last_signal: @last_signal,
|
|
192
|
+
strategies: @strategies.size,
|
|
193
|
+
callbacks: @signal_callbacks.size
|
|
194
|
+
}
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Reset the stream (clear all data)
|
|
199
|
+
def reset
|
|
200
|
+
@mutex.synchronize do
|
|
201
|
+
@data_buffer.each { |_, v| v.clear }
|
|
202
|
+
@indicator_cache.clear
|
|
203
|
+
@last_signal = :hold
|
|
204
|
+
@update_count = 0
|
|
205
|
+
end
|
|
206
|
+
self
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
private
|
|
210
|
+
|
|
211
|
+
# Check if we have enough data to run strategies
|
|
212
|
+
def sufficient_data?
|
|
213
|
+
@data_buffer[:prices].size >= 30 # Minimum data points
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Trim buffers to maintain window size
|
|
217
|
+
def trim_buffers
|
|
218
|
+
@data_buffer.each do |key, buffer|
|
|
219
|
+
buffer.shift while buffer.size > @window_size
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Process all strategies with current data
|
|
224
|
+
def process_strategies
|
|
225
|
+
return if @strategies.empty?
|
|
226
|
+
|
|
227
|
+
# Build vector with current market data and indicators
|
|
228
|
+
vector = build_vector
|
|
229
|
+
|
|
230
|
+
# Execute each strategy
|
|
231
|
+
signals = @strategies.map do |strategy|
|
|
232
|
+
begin
|
|
233
|
+
if strategy.is_a?(Class)
|
|
234
|
+
strategy.trade(vector)
|
|
235
|
+
elsif strategy.respond_to?(:call)
|
|
236
|
+
strategy.call(vector)
|
|
237
|
+
else
|
|
238
|
+
:hold
|
|
239
|
+
end
|
|
240
|
+
rescue => e
|
|
241
|
+
puts "Warning: Strategy #{strategy} failed: #{e.message}"
|
|
242
|
+
:hold
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Determine consensus signal
|
|
247
|
+
signal = consensus_signal(signals)
|
|
248
|
+
|
|
249
|
+
# Only emit if signal changed
|
|
250
|
+
if signal != @last_signal
|
|
251
|
+
@last_signal = signal
|
|
252
|
+
|
|
253
|
+
signal_data = {
|
|
254
|
+
signal: signal,
|
|
255
|
+
price: current_price,
|
|
256
|
+
timestamp: @data_buffer[:timestamps].last,
|
|
257
|
+
strategies_vote: signals.tally
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
# Notify signal callbacks
|
|
261
|
+
@signal_callbacks.each { |callback| callback.call(signal, signal_data) }
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Build data vector for strategy execution
|
|
266
|
+
def build_vector
|
|
267
|
+
require 'ostruct'
|
|
268
|
+
|
|
269
|
+
prices = @data_buffer[:prices].dup
|
|
270
|
+
volumes = @data_buffer[:volumes].dup
|
|
271
|
+
highs = @data_buffer[:highs].dup
|
|
272
|
+
lows = @data_buffer[:lows].dup
|
|
273
|
+
|
|
274
|
+
OpenStruct.new(
|
|
275
|
+
ticker: @ticker,
|
|
276
|
+
prices: prices,
|
|
277
|
+
volumes: volumes,
|
|
278
|
+
highs: highs,
|
|
279
|
+
lows: lows,
|
|
280
|
+
current_price: prices.last,
|
|
281
|
+
|
|
282
|
+
# Common indicators (lazy calculated)
|
|
283
|
+
rsi: lazy_indicator(:rsi, prices, period: 14),
|
|
284
|
+
sma: lazy_indicator(:sma, prices, period: 20),
|
|
285
|
+
ema: lazy_indicator(:ema, prices, period: 20),
|
|
286
|
+
macd: lazy_indicator(:macd, prices),
|
|
287
|
+
stoch_k: lazy_indicator(:stoch, highs, lows, prices, &:first),
|
|
288
|
+
stoch_d: lazy_indicator(:stoch, highs, lows, prices, &:last),
|
|
289
|
+
bb_upper: lazy_indicator(:bbands, prices, &:first),
|
|
290
|
+
bb_middle: lazy_indicator(:bbands, prices) { |r| r[1] },
|
|
291
|
+
bb_lower: lazy_indicator(:bbands, prices) { |r| r[2] }
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Lazy indicator calculation (only if accessed)
|
|
296
|
+
def lazy_indicator(name, *args, **kwargs, &extractor)
|
|
297
|
+
lambda do
|
|
298
|
+
result = calculate_indicator(name, *args, **kwargs)
|
|
299
|
+
extractor ? extractor.call(result) : result
|
|
300
|
+
end.call
|
|
301
|
+
rescue => e
|
|
302
|
+
nil
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Calculate technical indicator
|
|
306
|
+
def calculate_indicator(name, prices, volumes = nil, highs = nil, lows = nil, **options)
|
|
307
|
+
case name
|
|
308
|
+
when :rsi
|
|
309
|
+
SQAI.rsi(prices, **options)
|
|
310
|
+
when :sma
|
|
311
|
+
SQAI.sma(prices, **options)
|
|
312
|
+
when :ema
|
|
313
|
+
SQAI.ema(prices, **options)
|
|
314
|
+
when :macd
|
|
315
|
+
SQAI.macd(prices, **options)
|
|
316
|
+
when :stoch
|
|
317
|
+
SQAI.stoch(highs, lows, prices, **options)
|
|
318
|
+
when :bbands
|
|
319
|
+
SQAI.bbands(prices, **options)
|
|
320
|
+
when :adx
|
|
321
|
+
SQAI.adx(highs, lows, prices, **options)
|
|
322
|
+
when :atr
|
|
323
|
+
SQAI.atr(highs, lows, prices, **options)
|
|
324
|
+
else
|
|
325
|
+
# Try to call indicator directly
|
|
326
|
+
if SQAI.respond_to?(name)
|
|
327
|
+
SQAI.send(name, prices, **options)
|
|
328
|
+
else
|
|
329
|
+
raise "Unknown indicator: #{name}"
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Determine consensus signal from multiple strategy outputs
|
|
335
|
+
def consensus_signal(signals)
|
|
336
|
+
votes = signals.tally
|
|
337
|
+
buy_votes = votes[:buy] || 0
|
|
338
|
+
sell_votes = votes[:sell] || 0
|
|
339
|
+
hold_votes = votes[:hold] || 0
|
|
340
|
+
|
|
341
|
+
# Majority wins
|
|
342
|
+
if buy_votes > sell_votes && buy_votes > hold_votes
|
|
343
|
+
:buy
|
|
344
|
+
elsif sell_votes > buy_votes && sell_votes > hold_votes
|
|
345
|
+
:sell
|
|
346
|
+
else
|
|
347
|
+
:hold
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Calculate price range in buffer
|
|
352
|
+
def price_range
|
|
353
|
+
prices = @data_buffer[:prices]
|
|
354
|
+
return nil if prices.empty?
|
|
355
|
+
|
|
356
|
+
min = prices.min
|
|
357
|
+
max = prices.max
|
|
358
|
+
{ min: min, max: max, range: max - min }
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
data/lib/sqa/version.rb
CHANGED
data/lib/sqa.rb
CHANGED
|
@@ -30,17 +30,16 @@ $DEBUG_ME = true
|
|
|
30
30
|
#############################################
|
|
31
31
|
## Additional Libraries
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
require '
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
begin
|
|
34
|
+
require 'amazing_print'
|
|
35
|
+
rescue LoadError
|
|
36
|
+
# amazing_print is optional (development dependency)
|
|
37
|
+
end
|
|
37
38
|
require 'faraday'
|
|
38
39
|
require 'hashie'
|
|
39
40
|
require 'lite/statistics'
|
|
40
41
|
require 'lite/statistics/monkey_patches' # patch to Enumerable
|
|
41
42
|
require 'nenv'
|
|
42
|
-
require 'sem_version'
|
|
43
|
-
require 'sem_version/core_ext'
|
|
44
43
|
require 'tty-table'
|
|
45
44
|
|
|
46
45
|
|
|
@@ -51,9 +50,11 @@ require 'tty-table'
|
|
|
51
50
|
# ActiveSupport
|
|
52
51
|
require_relative "patches/string.rb"
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
|
|
54
|
+
#############################################
|
|
55
|
+
## API wrappers for External Websites
|
|
56
|
+
|
|
57
|
+
require_relative "api/alpha_vantage_api"
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
#############################################
|
|
@@ -64,19 +65,25 @@ require_relative "sqa/errors"
|
|
|
64
65
|
|
|
65
66
|
require_relative 'sqa/init.rb'
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
# TODO: Some of these components make direct calls to the
|
|
69
|
-
# Alpha Vantage API. Convert them to use the
|
|
70
|
-
# alphavantage gem.
|
|
71
|
-
|
|
72
68
|
require_relative "sqa/config"
|
|
73
|
-
require_relative "sqa/constants" # SMELL: more app than gem
|
|
74
69
|
require_relative "sqa/data_frame"
|
|
75
70
|
require_relative "sqa/indicator"
|
|
71
|
+
require_relative "sqa/fpop"
|
|
72
|
+
require_relative "sqa/market_regime"
|
|
73
|
+
require_relative "sqa/seasonal_analyzer"
|
|
74
|
+
require_relative "sqa/sector_analyzer"
|
|
76
75
|
require_relative "sqa/portfolio"
|
|
76
|
+
require_relative "sqa/backtest"
|
|
77
77
|
require_relative "sqa/strategy"
|
|
78
78
|
require_relative "sqa/stock"
|
|
79
79
|
require_relative "sqa/ticker"
|
|
80
|
-
require_relative "sqa/
|
|
80
|
+
require_relative "sqa/stream"
|
|
81
|
+
require_relative "sqa/gp"
|
|
82
|
+
require_relative "sqa/strategy_generator"
|
|
83
|
+
require_relative "sqa/risk_manager"
|
|
84
|
+
require_relative "sqa/portfolio_optimizer"
|
|
85
|
+
require_relative "sqa/ensemble"
|
|
86
|
+
require_relative "sqa/multi_timeframe"
|
|
87
|
+
require_relative "sqa/pattern_matcher"
|
|
81
88
|
|
|
82
89
|
|
data/main.just
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# aia/main.just
|
|
2
|
+
#
|
|
3
|
+
# Support man pages with ...
|
|
4
|
+
# gem install kramdown-man
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
RR := env_var('RR')
|
|
8
|
+
|
|
9
|
+
with ~/.justfile
|
|
10
|
+
|
|
11
|
+
# FIXME: justprep module process still has an issue with ~ and $HOME
|
|
12
|
+
# FIXME: justprep does not like more than one space between module name and path.
|
|
13
|
+
|
|
14
|
+
module repo /Users/dewayne/sandbox/git_repos/repo.just
|
|
15
|
+
module gem /Users/dewayne/sandbox/git_repos/gem.just
|
|
16
|
+
module version /Users/dewayne/just_modules/version.just
|
|
17
|
+
module git /Users/dewayne/just_modules/git.just
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Install Locally
|
|
21
|
+
install: update_toc_in_readmen create_man_page flay
|
|
22
|
+
rake install
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Create the TOC
|
|
26
|
+
update_toc_in_readmen:
|
|
27
|
+
rake toc
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Preview man page
|
|
31
|
+
preview_man_page:
|
|
32
|
+
kramdown-man {{RR}}/man/aia.1.md
|
|
33
|
+
|
|
34
|
+
# Static Code Check
|
|
35
|
+
flay: coverage
|
|
36
|
+
flay {{RR}}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# View coverage report
|
|
40
|
+
coverage: test
|
|
41
|
+
open {{RR}}/coverage/index.html
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Run Unit Tests
|
|
45
|
+
test:
|
|
46
|
+
rake test
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# View man page
|
|
50
|
+
view_man_page: create_man_page
|
|
51
|
+
man {{RR}}/man/aia.1
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Create man page
|
|
55
|
+
create_man_page:
|
|
56
|
+
rake man
|
|
57
|
+
|
|
58
|
+
##########################################
|
|
59
|
+
|
|
60
|
+
# Tag the current commit, push it, then bump the version
|
|
61
|
+
tag_push_and_bump: tag push bump
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Create a git tag for the current version
|
|
65
|
+
tag:
|
|
66
|
+
git tag $(semver)
|
|
67
|
+
|
|
68
|
+
# Push the git current working directory and all tags
|
|
69
|
+
push:
|
|
70
|
+
git push
|
|
71
|
+
git push origin --tags
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
alias inc := bump
|
|
75
|
+
|
|
76
|
+
# Increament version's level: major.minor.patch
|
|
77
|
+
@bump level='patch':
|
|
78
|
+
semver increment {{level}}
|
|
79
|
+
echo "Now working on: $(semver)"
|
|
80
|
+
git add {{RR}}/.semver
|
|
81
|
+
|