sqa 0.0.24 → 0.0.32
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 +95 -0
- data/CLAUDE.md +674 -0
- data/COMMITS.md +196 -0
- data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
- data/NEXT-STEPS.md +154 -0
- data/README.md +839 -265
- 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 +1114 -0
- data/docs/api/index.md +126 -0
- data/docs/assets/css/custom.css +88 -0
- data/docs/assets/images/sqa.jpg +0 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/concepts/index.md +60 -0
- data/docs/contributing/index.md +60 -0
- data/docs/data-sources/index.md +66 -0
- data/docs/data_frame.md +316 -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 +107 -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 +161 -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/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 +42 -0
- data/examples/sinatra_app/Gemfile.lock +268 -0
- data/examples/sinatra_app/QUICKSTART.md +169 -0
- data/examples/sinatra_app/README.md +471 -0
- data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +90 -0
- data/examples/sinatra_app/TROUBLESHOOTING.md +95 -0
- data/examples/sinatra_app/app.rb +404 -0
- data/examples/sinatra_app/config.ru +5 -0
- data/examples/sinatra_app/public/css/style.css +723 -0
- data/examples/sinatra_app/public/debug_macd.html +82 -0
- data/examples/sinatra_app/public/js/app.js +107 -0
- data/examples/sinatra_app/start.sh +53 -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 +831 -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 +51 -63
- 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 +154 -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 +16 -6
- 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 +131 -127
- 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/ticker.rb +9 -2
- data/lib/sqa/version.rb +1 -7
- data/lib/sqa.rb +35 -20
- data/main.just +81 -0
- data/mkdocs.yml +252 -0
- data/trace.log +0 -0
- metadata +265 -69
- data/bin/sqa +0 -6
- data/docs/alpha_vantage_technical_indicators.md +0 -62
- data/docs/average_true_range.md +0 -9
- data/docs/bollinger_bands.md +0 -15
- data/docs/candlestick_pattern_recognizer.md +0 -4
- data/docs/donchian_channel.md +0 -5
- data/docs/double_top_bottom_pattern.md +0 -3
- data/docs/exponential_moving_average.md +0 -19
- data/docs/fibonacci_retracement.md +0 -30
- data/docs/head_and_shoulders_pattern.md +0 -3
- data/docs/market_profile.md +0 -4
- data/docs/momentum.md +0 -19
- data/docs/moving_average_convergence_divergence.md +0 -23
- data/docs/peaks_and_valleys.md +0 -11
- data/docs/relative_strength_index.md +0 -6
- data/docs/simple_moving_average.md +0 -8
- data/docs/stochastic_oscillator.md +0 -4
- data/docs/ta_lib.md +0 -160
- data/docs/true_range.md +0 -12
- 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
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'boot'
|
|
4
|
+
|
|
5
|
+
require 'rails'
|
|
6
|
+
require 'active_model/railtie'
|
|
7
|
+
require 'active_job/railtie'
|
|
8
|
+
require 'active_record/railtie'
|
|
9
|
+
require 'action_controller/railtie'
|
|
10
|
+
require 'action_view/railtie'
|
|
11
|
+
require 'action_cable/railtie'
|
|
12
|
+
require 'rails/test_unit/railtie'
|
|
13
|
+
|
|
14
|
+
# Require the gems listed in Gemfile
|
|
15
|
+
Bundler.require(*Rails.groups)
|
|
16
|
+
|
|
17
|
+
module SqaRailsApp
|
|
18
|
+
class Application < Rails::Application
|
|
19
|
+
# Initialize configuration defaults for Rails 7.1
|
|
20
|
+
config.load_defaults 7.1
|
|
21
|
+
|
|
22
|
+
# Configuration for the application, engines, and railties
|
|
23
|
+
config.autoload_lib(ignore: %w(assets tasks))
|
|
24
|
+
|
|
25
|
+
# Add SQA library to load path
|
|
26
|
+
lib_path = File.expand_path('../../../lib', __dir__)
|
|
27
|
+
$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
|
|
28
|
+
|
|
29
|
+
# API-only mode for API namespace
|
|
30
|
+
config.api_only = false
|
|
31
|
+
|
|
32
|
+
# Enable sessions
|
|
33
|
+
config.session_store :cookie_store, key: '_sqa_rails_session'
|
|
34
|
+
|
|
35
|
+
# Assets
|
|
36
|
+
config.assets.paths << Rails.root.join('app', 'assets', 'stylesheets')
|
|
37
|
+
config.assets.paths << Rails.root.join('app', 'assets', 'javascripts')
|
|
38
|
+
|
|
39
|
+
# Time zone
|
|
40
|
+
config.time_zone = 'UTC'
|
|
41
|
+
|
|
42
|
+
# Eager load in production
|
|
43
|
+
config.eager_load = Rails.env.production?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# SQLite3 database configuration
|
|
2
|
+
|
|
3
|
+
default: &default
|
|
4
|
+
adapter: sqlite3
|
|
5
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
6
|
+
timeout: 5000
|
|
7
|
+
|
|
8
|
+
development:
|
|
9
|
+
<<: *default
|
|
10
|
+
database: db/development.sqlite3
|
|
11
|
+
|
|
12
|
+
test:
|
|
13
|
+
<<: *default
|
|
14
|
+
database: db/test.sqlite3
|
|
15
|
+
|
|
16
|
+
production:
|
|
17
|
+
<<: *default
|
|
18
|
+
database: db/production.sqlite3
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Rails.application.routes.draw do
|
|
4
|
+
# Root route
|
|
5
|
+
root 'dashboard#index'
|
|
6
|
+
|
|
7
|
+
# Dashboard routes
|
|
8
|
+
get 'dashboard/:ticker', to: 'dashboard#show', as: :stock_dashboard
|
|
9
|
+
get 'analyze/:ticker', to: 'analysis#show', as: :stock_analysis
|
|
10
|
+
get 'backtest/:ticker', to: 'backtest#show', as: :stock_backtest
|
|
11
|
+
get 'portfolio', to: 'portfolio#index', as: :portfolio
|
|
12
|
+
|
|
13
|
+
# API routes
|
|
14
|
+
namespace :api do
|
|
15
|
+
namespace :v1 do
|
|
16
|
+
get 'stock/:ticker', to: 'stocks#show'
|
|
17
|
+
get 'indicators/:ticker', to: 'stocks#indicators'
|
|
18
|
+
post 'backtest/:ticker', to: 'stocks#backtest'
|
|
19
|
+
get 'analyze/:ticker', to: 'stocks#analyze'
|
|
20
|
+
post 'compare/:ticker', to: 'stocks#compare'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Health check
|
|
25
|
+
get 'up', to: 'rails/health#show', as: :rails_health_check
|
|
26
|
+
end
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Real-Time Stock Price Streaming
|
|
5
|
+
#
|
|
6
|
+
# This example shows how to use SQA::Stream to process live stock
|
|
7
|
+
# price updates and generate trading signals in real-time.
|
|
8
|
+
|
|
9
|
+
require 'sqa'
|
|
10
|
+
|
|
11
|
+
SQA.init
|
|
12
|
+
|
|
13
|
+
puts "=" * 60
|
|
14
|
+
puts "Real-Time Stock Price Streaming Example"
|
|
15
|
+
puts "=" * 60
|
|
16
|
+
puts
|
|
17
|
+
|
|
18
|
+
# Example 1: Basic Streaming with Single Strategy
|
|
19
|
+
puts "\n" + "=" * 60
|
|
20
|
+
puts "Example 1: Basic Streaming Setup"
|
|
21
|
+
puts "=" * 60
|
|
22
|
+
puts
|
|
23
|
+
|
|
24
|
+
stream = SQA::Stream.new(
|
|
25
|
+
ticker: 'AAPL',
|
|
26
|
+
window_size: 100,
|
|
27
|
+
strategies: [SQA::Strategy::RSI]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
puts "Stream initialized:"
|
|
31
|
+
puts " Ticker: #{stream.ticker}"
|
|
32
|
+
puts " Window Size: #{stream.window_size}"
|
|
33
|
+
puts " Strategies: #{stream.strategies.size}"
|
|
34
|
+
puts
|
|
35
|
+
|
|
36
|
+
# Add signal callback
|
|
37
|
+
stream.on_signal do |signal, data|
|
|
38
|
+
puts "🔔 SIGNAL: #{signal.upcase}"
|
|
39
|
+
puts " Price: $#{data[:price].round(2)}"
|
|
40
|
+
puts " Time: #{data[:timestamp]}"
|
|
41
|
+
puts " Strategy Votes: #{data[:strategies_vote]}"
|
|
42
|
+
puts
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Add update callback (optional - for monitoring)
|
|
46
|
+
update_count = 0
|
|
47
|
+
stream.on_update do |data|
|
|
48
|
+
update_count += 1
|
|
49
|
+
if update_count % 10 == 0
|
|
50
|
+
puts "📊 Update ##{update_count}: Price=$#{data[:price].round(2)}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts "Callbacks registered. Simulating price updates..."
|
|
55
|
+
puts "-" * 60
|
|
56
|
+
|
|
57
|
+
# Simulate real-time price updates
|
|
58
|
+
# In production, these would come from a WebSocket or API
|
|
59
|
+
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
60
|
+
prices = stock.df["adj_close_price"].to_a
|
|
61
|
+
volumes = stock.df["volume"].to_a
|
|
62
|
+
highs = stock.df["high_price"].to_a
|
|
63
|
+
lows = stock.df["low_price"].to_a
|
|
64
|
+
|
|
65
|
+
# Stream the last 150 data points as if they were real-time
|
|
66
|
+
prices.last(150).each_with_index do |price, i|
|
|
67
|
+
stream.update(
|
|
68
|
+
price: price,
|
|
69
|
+
volume: volumes.last(150)[i],
|
|
70
|
+
high: highs.last(150)[i],
|
|
71
|
+
low: lows.last(150)[i],
|
|
72
|
+
timestamp: Time.now - (150 - i) * 60 # 1-minute intervals
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Simulate delay (in production, this happens naturally)
|
|
76
|
+
# sleep(0.01) # Uncomment for slower demo
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
puts "-" * 60
|
|
80
|
+
puts "Streaming complete. Final statistics:"
|
|
81
|
+
stats = stream.stats
|
|
82
|
+
puts " Total Updates: #{stats[:updates]}"
|
|
83
|
+
puts " Current Price: $#{stats[:current_price].round(2)}"
|
|
84
|
+
puts " Price Range: $#{stats[:price_range][:min].round(2)} - $#{stats[:price_range][:max].round(2)}"
|
|
85
|
+
puts " Last Signal: #{stats[:last_signal].upcase}"
|
|
86
|
+
puts
|
|
87
|
+
|
|
88
|
+
# Example 2: Multi-Strategy Streaming
|
|
89
|
+
puts "\n" + "=" * 60
|
|
90
|
+
puts "Example 2: Multi-Strategy Consensus"
|
|
91
|
+
puts "=" * 60
|
|
92
|
+
puts
|
|
93
|
+
|
|
94
|
+
multi_stream = SQA::Stream.new(
|
|
95
|
+
ticker: 'MSFT',
|
|
96
|
+
window_size: 100,
|
|
97
|
+
strategies: [
|
|
98
|
+
SQA::Strategy::RSI,
|
|
99
|
+
SQA::Strategy::MACD,
|
|
100
|
+
SQA::Strategy::BollingerBands,
|
|
101
|
+
SQA::Strategy::Stochastic
|
|
102
|
+
]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
puts "Multi-strategy stream configured with:"
|
|
106
|
+
multi_stream.strategies.each_with_index do |strategy, i|
|
|
107
|
+
puts " #{i + 1}. #{strategy}"
|
|
108
|
+
end
|
|
109
|
+
puts
|
|
110
|
+
|
|
111
|
+
signal_log = []
|
|
112
|
+
|
|
113
|
+
multi_stream.on_signal do |signal, data|
|
|
114
|
+
signal_log << { signal: signal, price: data[:price], votes: data[:strategies_vote] }
|
|
115
|
+
puts "🎯 CONSENSUS SIGNAL: #{signal.upcase}"
|
|
116
|
+
puts " Price: $#{data[:price].round(2)}"
|
|
117
|
+
puts " Strategy Votes: #{data[:strategies_vote]}"
|
|
118
|
+
puts " Time: #{data[:timestamp].strftime('%Y-%m-%d %H:%M:%S')}"
|
|
119
|
+
puts
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
puts "Simulating market data stream..."
|
|
123
|
+
puts "-" * 60
|
|
124
|
+
|
|
125
|
+
# Simulate streaming
|
|
126
|
+
stock2 = SQA::Stock.new(ticker: 'MSFT')
|
|
127
|
+
prices2 = stock2.df["adj_close_price"].to_a
|
|
128
|
+
volumes2 = stock2.df["volume"].to_a
|
|
129
|
+
|
|
130
|
+
prices2.last(100).each_with_index do |price, i|
|
|
131
|
+
multi_stream.update(
|
|
132
|
+
price: price,
|
|
133
|
+
volume: volumes2.last(100)[i],
|
|
134
|
+
timestamp: Time.now - (100 - i) * 60
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
puts "-" * 60
|
|
139
|
+
puts "Multi-strategy streaming complete."
|
|
140
|
+
puts "Signal summary:"
|
|
141
|
+
signal_log.each_with_index do |log, i|
|
|
142
|
+
puts " #{i + 1}. #{log[:signal].to_s.upcase} at $#{log[:price].round(2)} (votes: #{log[:votes]})"
|
|
143
|
+
end
|
|
144
|
+
puts
|
|
145
|
+
|
|
146
|
+
# Example 3: Custom Indicator Access
|
|
147
|
+
puts "\n" + "=" * 60
|
|
148
|
+
puts "Example 3: Accessing Real-Time Indicators"
|
|
149
|
+
puts "=" * 60
|
|
150
|
+
puts
|
|
151
|
+
|
|
152
|
+
indicator_stream = SQA::Stream.new(
|
|
153
|
+
ticker: 'GOOGL',
|
|
154
|
+
window_size: 50
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Stream some data first
|
|
158
|
+
stock3 = SQA::Stock.new(ticker: 'GOOGL')
|
|
159
|
+
stock3.df["adj_close_price"].to_a.last(50).each do |price|
|
|
160
|
+
indicator_stream.update(price: price)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
puts "Current market data:"
|
|
164
|
+
puts " Current Price: $#{indicator_stream.current_price.round(2)}"
|
|
165
|
+
puts " Recent Prices (last 5): #{indicator_stream.recent_prices(5).map { |p| p.round(2) }}"
|
|
166
|
+
puts
|
|
167
|
+
|
|
168
|
+
puts "Real-time indicators:"
|
|
169
|
+
rsi = indicator_stream.indicator(:rsi, period: 14)
|
|
170
|
+
sma_20 = indicator_stream.indicator(:sma, period: 20)
|
|
171
|
+
ema_12 = indicator_stream.indicator(:ema, period: 12)
|
|
172
|
+
macd_data = indicator_stream.indicator(:macd)
|
|
173
|
+
|
|
174
|
+
puts " RSI(14): #{rsi.last.round(2)}"
|
|
175
|
+
puts " SMA(20): $#{sma_20.last.round(2)}"
|
|
176
|
+
puts " EMA(12): $#{ema_12.last.round(2)}"
|
|
177
|
+
puts " MACD: #{macd_data[0].last.round(4)}"
|
|
178
|
+
puts " Signal: #{macd_data[1].last.round(4)}"
|
|
179
|
+
puts
|
|
180
|
+
|
|
181
|
+
# Example 4: Production-Style WebSocket Simulation
|
|
182
|
+
puts "\n" + "=" * 60
|
|
183
|
+
puts "Example 4: Production WebSocket Pattern"
|
|
184
|
+
puts "=" * 60
|
|
185
|
+
puts
|
|
186
|
+
|
|
187
|
+
class MockWebSocket
|
|
188
|
+
def initialize(ticker)
|
|
189
|
+
@ticker = ticker
|
|
190
|
+
@stock = SQA::Stock.new(ticker: ticker)
|
|
191
|
+
@prices = @stock.df["adj_close_price"].to_a
|
|
192
|
+
@index = @prices.size - 100
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def on_message(&block)
|
|
196
|
+
@message_handler = block
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def start
|
|
200
|
+
puts "📡 WebSocket connected to #{@ticker} market data feed"
|
|
201
|
+
puts " Streaming live prices..."
|
|
202
|
+
puts
|
|
203
|
+
|
|
204
|
+
# Simulate receiving messages
|
|
205
|
+
(@index...@prices.size).each do |i|
|
|
206
|
+
# Simulate WebSocket message
|
|
207
|
+
message = {
|
|
208
|
+
symbol: @ticker,
|
|
209
|
+
price: @prices[i],
|
|
210
|
+
volume: rand(1_000_000..10_000_000),
|
|
211
|
+
timestamp: Time.now
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@message_handler.call(message) if @message_handler
|
|
215
|
+
|
|
216
|
+
# sleep(0.1) # Uncomment for slower demo
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
puts " WebSocket closed"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Initialize stream for production pattern
|
|
224
|
+
production_stream = SQA::Stream.new(
|
|
225
|
+
ticker: 'TSLA',
|
|
226
|
+
window_size: 100,
|
|
227
|
+
strategies: [
|
|
228
|
+
SQA::Strategy::RSI,
|
|
229
|
+
SQA::Strategy::MACD,
|
|
230
|
+
SQA::Strategy::BollingerBands
|
|
231
|
+
]
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Set up signal handling
|
|
235
|
+
production_stream.on_signal do |signal, data|
|
|
236
|
+
# In production, this would trigger:
|
|
237
|
+
# - Alert notifications
|
|
238
|
+
# - Automated trading execution
|
|
239
|
+
# - Logging to database
|
|
240
|
+
# - Risk management checks
|
|
241
|
+
|
|
242
|
+
puts "⚡ TRADING SIGNAL: #{signal.upcase}"
|
|
243
|
+
puts " Execute #{signal} order for #{data[:price]}"
|
|
244
|
+
puts " Consensus: #{data[:strategies_vote]}"
|
|
245
|
+
puts
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Set up WebSocket (mock)
|
|
249
|
+
ws = MockWebSocket.new('TSLA')
|
|
250
|
+
|
|
251
|
+
ws.on_message do |message|
|
|
252
|
+
# Feed WebSocket data into stream processor
|
|
253
|
+
production_stream.update(
|
|
254
|
+
price: message[:price],
|
|
255
|
+
volume: message[:volume],
|
|
256
|
+
timestamp: message[:timestamp]
|
|
257
|
+
)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Start streaming
|
|
261
|
+
ws.start
|
|
262
|
+
|
|
263
|
+
puts "-" * 60
|
|
264
|
+
puts "Production simulation complete."
|
|
265
|
+
puts
|
|
266
|
+
|
|
267
|
+
puts "All streaming examples complete!"
|
|
268
|
+
puts
|
|
269
|
+
puts "In production, connect to real WebSocket feeds from:"
|
|
270
|
+
puts " - Alpha Vantage WebSocket API"
|
|
271
|
+
puts " - Yahoo Finance Streaming"
|
|
272
|
+
puts " - Interactive Brokers TWS API"
|
|
273
|
+
puts " - Polygon.io WebSocket"
|
|
274
|
+
puts " - Alpaca Markets WebSocket"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
ruby '>= 3.2.0'
|
|
6
|
+
|
|
7
|
+
# Web framework
|
|
8
|
+
gem 'rackup'
|
|
9
|
+
gem 'sinatra', '~> 4.0'
|
|
10
|
+
gem 'sinatra-contrib', '~> 4.0'
|
|
11
|
+
gem 'puma', '~> 6.0'
|
|
12
|
+
|
|
13
|
+
# JSON handling
|
|
14
|
+
gem 'json', '~> 2.7'
|
|
15
|
+
|
|
16
|
+
# SQA library dependencies (required when loading SQA from local path)
|
|
17
|
+
gem 'alphavantage'
|
|
18
|
+
gem 'csv'
|
|
19
|
+
gem 'faraday'
|
|
20
|
+
gem 'hashie'
|
|
21
|
+
gem 'kbs'
|
|
22
|
+
gem 'lite-statistics'
|
|
23
|
+
gem 'nenv'
|
|
24
|
+
gem 'redis'
|
|
25
|
+
gem 'ruby_llm'
|
|
26
|
+
gem 'ruby_llm-mcp'
|
|
27
|
+
gem 'shared_tools'
|
|
28
|
+
gem 'sqa-tai'
|
|
29
|
+
gem 'tty-table'
|
|
30
|
+
gem 'eps'
|
|
31
|
+
gem 'polars-df'
|
|
32
|
+
gem 'toml-rb'
|
|
33
|
+
gem 'regent'
|
|
34
|
+
|
|
35
|
+
# SQA library (assumes it's in parent directory)
|
|
36
|
+
# In production, this would be:
|
|
37
|
+
# gem 'sqa'
|
|
38
|
+
# For development, we'll load from local path via require in app.rb
|
|
39
|
+
|
|
40
|
+
group :development do
|
|
41
|
+
gem 'rerun' # Auto-reload on file changes
|
|
42
|
+
end
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
addressable (2.8.7)
|
|
5
|
+
public_suffix (>= 2.0.2, < 7.0)
|
|
6
|
+
alphavantage (1.2.0)
|
|
7
|
+
faraday (~> 1.4)
|
|
8
|
+
hashie (~> 4.1)
|
|
9
|
+
base64 (0.3.0)
|
|
10
|
+
bigdecimal (3.3.1)
|
|
11
|
+
citrus (3.0.2)
|
|
12
|
+
concurrent-ruby (1.3.5)
|
|
13
|
+
connection_pool (2.5.4)
|
|
14
|
+
csv (3.3.5)
|
|
15
|
+
dentaku (3.5.6)
|
|
16
|
+
bigdecimal
|
|
17
|
+
concurrent-ruby
|
|
18
|
+
eps (0.6.0)
|
|
19
|
+
lightgbm (>= 0.1.7)
|
|
20
|
+
matrix
|
|
21
|
+
nokogiri
|
|
22
|
+
event_stream_parser (1.0.0)
|
|
23
|
+
faraday (1.10.4)
|
|
24
|
+
faraday-em_http (~> 1.0)
|
|
25
|
+
faraday-em_synchrony (~> 1.0)
|
|
26
|
+
faraday-excon (~> 1.1)
|
|
27
|
+
faraday-httpclient (~> 1.0)
|
|
28
|
+
faraday-multipart (~> 1.0)
|
|
29
|
+
faraday-net_http (~> 1.0)
|
|
30
|
+
faraday-net_http_persistent (~> 1.0)
|
|
31
|
+
faraday-patron (~> 1.0)
|
|
32
|
+
faraday-rack (~> 1.0)
|
|
33
|
+
faraday-retry (~> 1.0)
|
|
34
|
+
ruby2_keywords (>= 0.0.4)
|
|
35
|
+
faraday-em_http (1.0.0)
|
|
36
|
+
faraday-em_synchrony (1.0.1)
|
|
37
|
+
faraday-excon (1.1.0)
|
|
38
|
+
faraday-httpclient (1.0.1)
|
|
39
|
+
faraday-multipart (1.1.1)
|
|
40
|
+
multipart-post (~> 2.0)
|
|
41
|
+
faraday-net_http (1.0.2)
|
|
42
|
+
faraday-net_http_persistent (1.2.0)
|
|
43
|
+
faraday-patron (1.0.0)
|
|
44
|
+
faraday-rack (1.0.0)
|
|
45
|
+
faraday-retry (1.0.3)
|
|
46
|
+
ffi (1.17.2)
|
|
47
|
+
ffi (1.17.2-aarch64-linux-gnu)
|
|
48
|
+
ffi (1.17.2-aarch64-linux-musl)
|
|
49
|
+
ffi (1.17.2-arm-linux-gnu)
|
|
50
|
+
ffi (1.17.2-arm-linux-musl)
|
|
51
|
+
ffi (1.17.2-arm64-darwin)
|
|
52
|
+
ffi (1.17.2-x86-linux-gnu)
|
|
53
|
+
ffi (1.17.2-x86-linux-musl)
|
|
54
|
+
ffi (1.17.2-x86_64-darwin)
|
|
55
|
+
ffi (1.17.2-x86_64-linux-gnu)
|
|
56
|
+
ffi (1.17.2-x86_64-linux-musl)
|
|
57
|
+
fiddle (1.1.8)
|
|
58
|
+
hashie (4.1.0)
|
|
59
|
+
http-2 (1.1.1)
|
|
60
|
+
httpx (1.6.3)
|
|
61
|
+
http-2 (>= 1.0.0)
|
|
62
|
+
json (2.16.0)
|
|
63
|
+
json-schema (5.2.2)
|
|
64
|
+
addressable (~> 2.8)
|
|
65
|
+
bigdecimal (~> 3.1)
|
|
66
|
+
kbs (0.1.0)
|
|
67
|
+
sqlite3 (~> 1.6)
|
|
68
|
+
lightgbm (0.4.3)
|
|
69
|
+
ffi
|
|
70
|
+
listen (3.9.0)
|
|
71
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
72
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
|
73
|
+
lite-memoize (1.1.1)
|
|
74
|
+
lite-statistics (2.0.0)
|
|
75
|
+
lite-memoize
|
|
76
|
+
logger (1.7.0)
|
|
77
|
+
marcel (1.1.0)
|
|
78
|
+
matrix (0.4.3)
|
|
79
|
+
mini_portile2 (2.8.9)
|
|
80
|
+
multi_json (1.17.0)
|
|
81
|
+
multipart-post (2.4.1)
|
|
82
|
+
mustermann (3.0.4)
|
|
83
|
+
ruby2_keywords (~> 0.0.1)
|
|
84
|
+
nenv (0.3.0)
|
|
85
|
+
nio4r (2.7.5)
|
|
86
|
+
nokogiri (1.18.10)
|
|
87
|
+
mini_portile2 (~> 2.8.2)
|
|
88
|
+
racc (~> 1.4)
|
|
89
|
+
nokogiri (1.18.10-aarch64-linux-gnu)
|
|
90
|
+
racc (~> 1.4)
|
|
91
|
+
nokogiri (1.18.10-aarch64-linux-musl)
|
|
92
|
+
racc (~> 1.4)
|
|
93
|
+
nokogiri (1.18.10-arm-linux-gnu)
|
|
94
|
+
racc (~> 1.4)
|
|
95
|
+
nokogiri (1.18.10-arm-linux-musl)
|
|
96
|
+
racc (~> 1.4)
|
|
97
|
+
nokogiri (1.18.10-arm64-darwin)
|
|
98
|
+
racc (~> 1.4)
|
|
99
|
+
nokogiri (1.18.10-x86_64-darwin)
|
|
100
|
+
racc (~> 1.4)
|
|
101
|
+
nokogiri (1.18.10-x86_64-linux-gnu)
|
|
102
|
+
racc (~> 1.4)
|
|
103
|
+
nokogiri (1.18.10-x86_64-linux-musl)
|
|
104
|
+
racc (~> 1.4)
|
|
105
|
+
openweathermap (0.2.3)
|
|
106
|
+
pastel (0.8.0)
|
|
107
|
+
tty-color (~> 0.5)
|
|
108
|
+
polars-df (0.23.0)
|
|
109
|
+
bigdecimal
|
|
110
|
+
rb_sys
|
|
111
|
+
polars-df (0.23.0-aarch64-linux)
|
|
112
|
+
bigdecimal
|
|
113
|
+
polars-df (0.23.0-aarch64-linux-musl)
|
|
114
|
+
bigdecimal
|
|
115
|
+
polars-df (0.23.0-arm64-darwin)
|
|
116
|
+
bigdecimal
|
|
117
|
+
polars-df (0.23.0-x86_64-darwin)
|
|
118
|
+
bigdecimal
|
|
119
|
+
polars-df (0.23.0-x86_64-linux)
|
|
120
|
+
bigdecimal
|
|
121
|
+
polars-df (0.23.0-x86_64-linux-musl)
|
|
122
|
+
bigdecimal
|
|
123
|
+
public_suffix (6.0.2)
|
|
124
|
+
puma (6.6.1)
|
|
125
|
+
nio4r (~> 2.0)
|
|
126
|
+
racc (1.8.1)
|
|
127
|
+
rack (3.2.4)
|
|
128
|
+
rack-protection (4.2.1)
|
|
129
|
+
base64 (>= 0.1.0)
|
|
130
|
+
logger (>= 1.6.0)
|
|
131
|
+
rack (>= 3.0.0, < 4)
|
|
132
|
+
rack-session (2.1.1)
|
|
133
|
+
base64 (>= 0.1.0)
|
|
134
|
+
rack (>= 3.0.0)
|
|
135
|
+
rackup (2.2.1)
|
|
136
|
+
rack (>= 3)
|
|
137
|
+
rake-compiler-dock (1.9.1)
|
|
138
|
+
rb-fsevent (0.11.2)
|
|
139
|
+
rb-inotify (0.11.1)
|
|
140
|
+
ffi (~> 1.0)
|
|
141
|
+
rb_sys (0.9.117)
|
|
142
|
+
rake-compiler-dock (= 1.9.1)
|
|
143
|
+
redis (5.4.1)
|
|
144
|
+
redis-client (>= 0.22.0)
|
|
145
|
+
redis-client (0.26.1)
|
|
146
|
+
connection_pool
|
|
147
|
+
regent (0.3.3)
|
|
148
|
+
pastel (~> 0.8.0)
|
|
149
|
+
tty-spinner (~> 0.9.3)
|
|
150
|
+
zeitwerk (~> 2.7)
|
|
151
|
+
rerun (0.14.0)
|
|
152
|
+
listen (~> 3.0)
|
|
153
|
+
ruby2_keywords (0.0.5)
|
|
154
|
+
ruby_llm (1.9.1)
|
|
155
|
+
base64
|
|
156
|
+
event_stream_parser (~> 1)
|
|
157
|
+
faraday (>= 1.10.0)
|
|
158
|
+
faraday-multipart (>= 1)
|
|
159
|
+
faraday-net_http (>= 1)
|
|
160
|
+
faraday-retry (>= 1)
|
|
161
|
+
marcel (~> 1.0)
|
|
162
|
+
ruby_llm-schema (~> 0.2.1)
|
|
163
|
+
zeitwerk (~> 2)
|
|
164
|
+
ruby_llm-mcp (0.8.0)
|
|
165
|
+
httpx (~> 1.4)
|
|
166
|
+
json-schema (~> 5.0)
|
|
167
|
+
ruby_llm (~> 1.9)
|
|
168
|
+
zeitwerk (~> 2)
|
|
169
|
+
ruby_llm-schema (0.2.4)
|
|
170
|
+
sequel (5.98.0)
|
|
171
|
+
bigdecimal
|
|
172
|
+
shared_tools (0.3.0)
|
|
173
|
+
dentaku
|
|
174
|
+
nokogiri
|
|
175
|
+
openweathermap
|
|
176
|
+
ruby_llm
|
|
177
|
+
ruby_llm-mcp
|
|
178
|
+
sequel
|
|
179
|
+
zeitwerk
|
|
180
|
+
sinatra (4.2.1)
|
|
181
|
+
logger (>= 1.6.0)
|
|
182
|
+
mustermann (~> 3.0)
|
|
183
|
+
rack (>= 3.0.0, < 4)
|
|
184
|
+
rack-protection (= 4.2.1)
|
|
185
|
+
rack-session (>= 2.0.0, < 3)
|
|
186
|
+
tilt (~> 2.0)
|
|
187
|
+
sinatra-contrib (4.2.1)
|
|
188
|
+
multi_json (>= 0.0.2)
|
|
189
|
+
mustermann (~> 3.0)
|
|
190
|
+
rack-protection (= 4.2.1)
|
|
191
|
+
sinatra (= 4.2.1)
|
|
192
|
+
tilt (~> 2.0)
|
|
193
|
+
sqa-tai (0.1.0)
|
|
194
|
+
ta_lib_ffi (~> 0.3)
|
|
195
|
+
sqlite3 (1.7.3)
|
|
196
|
+
mini_portile2 (~> 2.8.0)
|
|
197
|
+
sqlite3 (1.7.3-aarch64-linux)
|
|
198
|
+
sqlite3 (1.7.3-arm-linux)
|
|
199
|
+
sqlite3 (1.7.3-arm64-darwin)
|
|
200
|
+
sqlite3 (1.7.3-x86-linux)
|
|
201
|
+
sqlite3 (1.7.3-x86_64-darwin)
|
|
202
|
+
sqlite3 (1.7.3-x86_64-linux)
|
|
203
|
+
strings (0.2.1)
|
|
204
|
+
strings-ansi (~> 0.2)
|
|
205
|
+
unicode-display_width (>= 1.5, < 3.0)
|
|
206
|
+
unicode_utils (~> 1.4)
|
|
207
|
+
strings-ansi (0.2.0)
|
|
208
|
+
ta_lib_ffi (0.3.0)
|
|
209
|
+
fiddle (~> 1.1)
|
|
210
|
+
tilt (2.6.1)
|
|
211
|
+
toml-rb (4.1.0)
|
|
212
|
+
citrus (~> 3.0, > 3.0)
|
|
213
|
+
racc (~> 1.7)
|
|
214
|
+
tty-color (0.6.0)
|
|
215
|
+
tty-cursor (0.7.1)
|
|
216
|
+
tty-screen (0.8.2)
|
|
217
|
+
tty-spinner (0.9.3)
|
|
218
|
+
tty-cursor (~> 0.7)
|
|
219
|
+
tty-table (0.12.0)
|
|
220
|
+
pastel (~> 0.8)
|
|
221
|
+
strings (~> 0.2.0)
|
|
222
|
+
tty-screen (~> 0.8)
|
|
223
|
+
unicode-display_width (2.6.0)
|
|
224
|
+
unicode_utils (1.4.0)
|
|
225
|
+
zeitwerk (2.7.3)
|
|
226
|
+
|
|
227
|
+
PLATFORMS
|
|
228
|
+
aarch64-linux-gnu
|
|
229
|
+
aarch64-linux-musl
|
|
230
|
+
arm-linux-gnu
|
|
231
|
+
arm-linux-musl
|
|
232
|
+
arm64-darwin
|
|
233
|
+
x86-linux-gnu
|
|
234
|
+
x86-linux-musl
|
|
235
|
+
x86_64-darwin
|
|
236
|
+
x86_64-linux-gnu
|
|
237
|
+
x86_64-linux-musl
|
|
238
|
+
|
|
239
|
+
DEPENDENCIES
|
|
240
|
+
alphavantage
|
|
241
|
+
csv
|
|
242
|
+
eps
|
|
243
|
+
faraday
|
|
244
|
+
hashie
|
|
245
|
+
json (~> 2.7)
|
|
246
|
+
kbs
|
|
247
|
+
lite-statistics
|
|
248
|
+
nenv
|
|
249
|
+
polars-df
|
|
250
|
+
puma (~> 6.0)
|
|
251
|
+
rackup
|
|
252
|
+
redis
|
|
253
|
+
regent
|
|
254
|
+
rerun
|
|
255
|
+
ruby_llm
|
|
256
|
+
ruby_llm-mcp
|
|
257
|
+
shared_tools
|
|
258
|
+
sinatra (~> 4.0)
|
|
259
|
+
sinatra-contrib (~> 4.0)
|
|
260
|
+
sqa-tai
|
|
261
|
+
toml-rb
|
|
262
|
+
tty-table
|
|
263
|
+
|
|
264
|
+
RUBY VERSION
|
|
265
|
+
ruby 3.3.6p108
|
|
266
|
+
|
|
267
|
+
BUNDLED WITH
|
|
268
|
+
2.7.2
|