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/README.md
CHANGED
|
@@ -2,422 +2,972 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.youtube.com/watch?v=VqomZQMZQCQ)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
The BUY/SELL signals that it generates should not be taken seriously. **DO NOT USE** this library when real money is at stake. If you lose your shirt playing in the stock market don't come crying to me. I think playing in the market is like playing in the street. You are going to get run over.
|
|
8
|
-
|
|
9
|
-
<!-- TODO: Consider these gems ...
|
|
10
|
-
|
|
11
|
-
This one is most likely out of date:
|
|
12
|
-
yahoofinance-ruby: This gem provides a simple Ruby interface to Yahoo Finance's historical quote data. If you're looking to add more data sources to your project, this could be a useful addition. yahoofinance-ruby
|
|
13
|
-
|
|
14
|
-
Worth Looking at:
|
|
15
|
-
ruby-technical-analysis: This gem provides various technical analysis calculations. It includes over 60 technical analysis functions and indicators like RSI, EMA, SMA, Bollinger Bands, MACD, and more. ruby-technical-analysis
|
|
16
|
-
|
|
17
|
-
Maybe later if I want to add an ability to make a live trade from within the SQA context...
|
|
18
|
-
ib-ruby: This gem provides a Ruby interface to Interactive Brokers' Trader Workstation (TWS) API, allowing you to build algorithmic trading strategies in Ruby. ib-ruby
|
|
19
|
-
|
|
20
|
-
Definitely looking for a plotting package.
|
|
21
|
-
plottable: If you're looking to add more visualization capabilities to your project, this gem could be useful. It provides a simple and flexible API for creating plots and charts in Ruby. plottable
|
|
22
|
-
|
|
23
|
-
Currently using CSV files; maybe switch to an RDBMS ...
|
|
24
|
-
activerecord-import: If you're dealing with large amounts of data, this gem could help improve performance. It adds methods to ActiveRecord for bulk inserting data into the database. activerecord-import
|
|
25
|
-
|
|
26
|
-
Could spawn off separate agents for each stock within a portfolio for analysis ...
|
|
27
|
-
parallel: This gem can help improve performance by allowing you to run code in parallel. This could be useful if you're running complex calculations on large datasets. parallel
|
|
28
|
-
|
|
29
|
-
-->
|
|
30
|
-
|
|
5
|
+
A Ruby library for technical analysis of stock market data. This is a simplistic set of tools for quantitative and qualitative stock analysis designed for **educational purposes only**.
|
|
31
6
|
|
|
7
|
+
**⚠️ WARNING:** This is a learning tool, not production software. Do NOT use this library when real money is at stake. The BUY/SELL signals it generates should not be taken seriously. If you lose your shirt playing in the stock market, don't come crying to me. Playing in the market is like playing in the street - you're going to get run over.
|
|
32
8
|
|
|
33
9
|
<!-- Tocer[start]: Auto-generated, don't remove. -->
|
|
34
10
|
|
|
35
11
|
## Table of Contents
|
|
36
12
|
|
|
37
|
-
- [
|
|
38
|
-
- [Recent Changes](#recent-changes)
|
|
13
|
+
- [Features](#features)
|
|
39
14
|
- [Installation](#installation)
|
|
40
|
-
- [
|
|
15
|
+
- [Configuration](#configuration)
|
|
16
|
+
- [Data Directory](#data-directory)
|
|
17
|
+
- [Alpha Vantage API Key](#alpha-vantage-api-key)
|
|
41
18
|
- [Usage](#usage)
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
19
|
+
- [Working with Stocks](#working-with-stocks)
|
|
20
|
+
- [DataFrame Operations](#dataframe-operations)
|
|
21
|
+
- [Technical Indicators](#technical-indicators)
|
|
22
|
+
- [Trading Strategies](#trading-strategies)
|
|
23
|
+
- [Statistics](#statistics)
|
|
24
|
+
- [Interactive Console](#interactive-console)
|
|
25
|
+
- [Architecture](#architecture)
|
|
26
|
+
- [Data Sources](#data-sources)
|
|
27
|
+
- [Alpha Vantage](#alpha-vantage)
|
|
45
28
|
- [Yahoo Finance](#yahoo-finance)
|
|
46
|
-
- [Playing in IRB](#playing-in-irb)
|
|
47
|
-
- [With Stocks and Indicators](#with-stocks-and-indicators)
|
|
48
|
-
- [Get Historical Prices](#get-historical-prices)
|
|
49
|
-
- [Playing in the IRB - Setup](#playing-in-the-irb---setup)
|
|
50
|
-
- [Playing in the IRB - Statistics](#playing-in-the-irb---statistics)
|
|
51
|
-
- [Playing in the IRB - Indicators](#playing-in-the-irb---indicators)
|
|
52
|
-
- [Playing in the IRB - Strategies](#playing-in-the-irb---strategies)
|
|
53
|
-
- [Included Program Examples](#included-program-examples)
|
|
54
|
-
- [Analysis](#analysis)
|
|
55
|
-
- [Web](#web)
|
|
56
|
-
- [Predicted FAQ](#predicted-faq)
|
|
57
|
-
- [Other Similar Projects](#other-similar-projects)
|
|
58
29
|
- [Contributing](#contributing)
|
|
59
30
|
- [License](#license)
|
|
60
31
|
|
|
61
32
|
<!-- Tocer[finish]: Auto-generated, don't remove. -->
|
|
62
33
|
|
|
63
|
-
##
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
### Core Capabilities
|
|
37
|
+
- **High-Performance DataFrames** - Polars-based data structures for time series financial data
|
|
38
|
+
- **150+ Technical Indicators** - Via the `sqa-tai` gem (TA-Lib wrapper)
|
|
39
|
+
- **Trading Strategies** - Framework for building and testing trading strategies
|
|
40
|
+
- **Multiple Data Sources** - Alpha Vantage and Yahoo Finance adapters
|
|
41
|
+
- **Stock Management** - Track stocks with historical prices and company metadata
|
|
42
|
+
- **Statistical Analysis** - Comprehensive statistics on price data
|
|
43
|
+
- **Ticker Validation** - Validate stock symbols against market exchanges
|
|
44
|
+
- **Interactive Console** - IRB console for experimentation (`sqa-console`)
|
|
45
|
+
|
|
46
|
+
### Advanced Features
|
|
47
|
+
- **Portfolio Management** - Track positions, trades, P&L with commission support
|
|
48
|
+
- **Backtesting Framework** - Simulate trading strategies with comprehensive performance metrics
|
|
49
|
+
- **Strategy Generator** - Reverse-engineer profitable trades to discover indicator patterns
|
|
50
|
+
- **Genetic Programming** - Evolve optimal strategy parameters through natural selection
|
|
51
|
+
- **Knowledge-Based Strategy (KBS)** - RETE-based forward-chaining inference for complex trading rules
|
|
52
|
+
- **Real-Time Streaming** - Event-driven live price processing with on-the-fly signal generation
|
|
53
|
+
- **FPL Analysis** - Future Period Loss/Profit analysis with risk metrics and directional classification
|
|
54
|
+
- **Market Regime Detection** - Identify bull/bear/sideways markets with volatility and strength metrics
|
|
55
|
+
- **Seasonal Analysis** - Discover calendar-dependent patterns (monthly/quarterly performance)
|
|
56
|
+
- **Sector Analysis** - Multi-stock pattern discovery with KBS blackboards per sector
|
|
57
|
+
- **Walk-Forward Validation** - Time-series cross-validation to prevent pattern overfitting
|
|
58
|
+
- **Pattern Context System** - Context-aware patterns that know when/where they're valid
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
## Installation
|
|
66
61
|
|
|
67
|
-
|
|
62
|
+
Install the gem:
|
|
68
63
|
|
|
69
|
-
|
|
64
|
+
```bash
|
|
65
|
+
gem install sqa
|
|
66
|
+
```
|
|
70
67
|
|
|
71
|
-
|
|
68
|
+
Or add to your `Gemfile`:
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
```ruby
|
|
71
|
+
gem 'sqa'
|
|
72
|
+
```
|
|
74
73
|
|
|
75
|
-
|
|
74
|
+
Then run:
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
```bash
|
|
77
|
+
bundle install
|
|
78
|
+
```
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
## Configuration
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
### Data Directory
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
SQA stores historical price data and metadata in a data directory. By default, it uses `~/sqa_data/`:
|
|
84
85
|
|
|
85
86
|
```ruby
|
|
86
|
-
|
|
87
|
-
sqa --version
|
|
87
|
+
require 'sqa'
|
|
88
88
|
|
|
89
|
-
#
|
|
89
|
+
# Initialize with default configuration
|
|
90
|
+
SQA.init
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
# Or specify a custom data directory
|
|
93
|
+
SQA::Config.new(data_dir: '~/Documents/my_stock_data')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Create your data directory:
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# dependencies for you.
|
|
98
|
+
```bash
|
|
99
|
+
mkdir ~/sqa_data
|
|
97
100
|
```
|
|
98
101
|
|
|
102
|
+
### Alpha Vantage API Key
|
|
103
|
+
|
|
104
|
+
SQA uses Alpha Vantage for stock data. You'll need a free API key from [https://www.alphavantage.co/](https://www.alphavantage.co/)
|
|
105
|
+
|
|
106
|
+
Set the environment variable:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
export AV_API_KEY="your_api_key_here"
|
|
110
|
+
# or
|
|
111
|
+
export ALPHAVANTAGE_API_KEY="your_api_key_here"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The free tier allows:
|
|
115
|
+
- 5 API calls per minute
|
|
116
|
+
- 100 API calls per day
|
|
117
|
+
|
|
99
118
|
## Usage
|
|
100
119
|
|
|
101
|
-
|
|
120
|
+
### Working with Stocks
|
|
102
121
|
|
|
103
|
-
|
|
122
|
+
```ruby
|
|
123
|
+
require 'sqa'
|
|
104
124
|
|
|
105
|
-
|
|
125
|
+
# Initialize SQA
|
|
126
|
+
SQA.init
|
|
106
127
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
128
|
+
# Create or load a stock (automatically fetches/updates data from Alpha Vantage)
|
|
129
|
+
aapl = SQA::Stock.new(ticker: 'AAPL', source: :alpha_vantage)
|
|
130
|
+
#=> aapl with 1207 data points from 2019-01-02 to 2023-10-17
|
|
110
131
|
|
|
111
|
-
|
|
132
|
+
# Access the DataFrame
|
|
133
|
+
aapl.df
|
|
134
|
+
#=> SQA::DataFrame (wraps Polars::DataFrame)
|
|
112
135
|
|
|
113
|
-
|
|
136
|
+
# Get column names
|
|
137
|
+
aapl.df.columns
|
|
138
|
+
#=> ["timestamp", "open_price", "high_price", "low_price", "close_price", "adj_close_price", "volume"]
|
|
114
139
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
-d, --debug Turn on debugging output
|
|
119
|
-
--dump-config path_to_file Dump the current configuration
|
|
120
|
-
-h, --help Print usage
|
|
121
|
-
-l, --log_level string Set the log level (debug, info, warn, error,
|
|
122
|
-
fatal)
|
|
123
|
-
-p, --portfolio string Set the filename of the portfolio
|
|
124
|
-
-t, --trades string Set the filename into which trades are
|
|
125
|
-
stored
|
|
126
|
-
-v, --verbose Print verbosely
|
|
127
|
-
--version Print version
|
|
140
|
+
# Access price data
|
|
141
|
+
aapl.df["adj_close_price"]
|
|
142
|
+
#=> Polars::Series with all adjusted closing prices
|
|
128
143
|
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
# Get recent prices
|
|
145
|
+
prices = aapl.df["adj_close_price"].to_a
|
|
146
|
+
prices.last(5)
|
|
147
|
+
#=> [179.8, 180.71, 178.85, 178.72, 177.15]
|
|
131
148
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
149
|
+
# Access company metadata
|
|
150
|
+
aapl.name
|
|
151
|
+
#=> "Apple Inc."
|
|
135
152
|
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
aapl.exchange
|
|
154
|
+
#=> "NASDAQ"
|
|
138
155
|
|
|
139
|
-
|
|
156
|
+
aapl.overview
|
|
157
|
+
#=> Hash with detailed company information
|
|
158
|
+
```
|
|
140
159
|
|
|
141
|
-
###
|
|
160
|
+
### DataFrame Operations
|
|
142
161
|
|
|
143
|
-
|
|
162
|
+
SQA::DataFrame is a wrapper around the high-performance Polars DataFrame:
|
|
144
163
|
|
|
145
164
|
```ruby
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
require 'sqa'
|
|
166
|
+
|
|
167
|
+
# Create from hash
|
|
168
|
+
data = {
|
|
169
|
+
timestamp: ['2023-01-01', '2023-01-02', '2023-01-03'],
|
|
170
|
+
price: [100.0, 101.5, 99.8]
|
|
171
|
+
}
|
|
172
|
+
df = SQA::DataFrame.new(data)
|
|
150
173
|
|
|
151
|
-
|
|
174
|
+
# Load from CSV
|
|
175
|
+
df = SQA::DataFrame.load(source: 'aapl.csv')
|
|
176
|
+
|
|
177
|
+
# Basic operations
|
|
178
|
+
df.size # Number of rows
|
|
179
|
+
df.ncols # Number of columns
|
|
180
|
+
df.to_h # Convert to hash
|
|
181
|
+
df.to_csv('output.csv') # Save to CSV
|
|
182
|
+
|
|
183
|
+
# Access underlying Polars DataFrame
|
|
184
|
+
df.data # Polars::DataFrame
|
|
185
|
+
```
|
|
152
186
|
|
|
153
|
-
|
|
187
|
+
### Technical Indicators
|
|
154
188
|
|
|
155
|
-
|
|
189
|
+
All technical indicators are provided by the [`sqa-tai`](https://github.com/MadBomber/sqa-tai) gem, which wraps the industry-standard TA-Lib library:
|
|
156
190
|
|
|
157
|
-
|
|
191
|
+
```ruby
|
|
192
|
+
require 'sqa'
|
|
158
193
|
|
|
159
|
-
|
|
194
|
+
prices = [100, 102, 105, 103, 107, 110, 108, 112, 115, 113]
|
|
160
195
|
|
|
161
|
-
|
|
196
|
+
# Simple Moving Average
|
|
197
|
+
sma = SQAI.sma(prices, period: 5)
|
|
198
|
+
#=> [104.0, 105.4, 106.6, 108.0, 110.4, 111.6]
|
|
162
199
|
|
|
163
|
-
|
|
200
|
+
# Relative Strength Index
|
|
201
|
+
rsi = SQAI.rsi(prices, period: 14)
|
|
202
|
+
#=> [70.5, 68.2, ...]
|
|
164
203
|
|
|
165
|
-
|
|
204
|
+
# Exponential Moving Average
|
|
205
|
+
ema = SQAI.ema(prices, period: 5)
|
|
206
|
+
#=> [...]
|
|
166
207
|
|
|
208
|
+
# Bollinger Bands (returns upper, middle, lower)
|
|
209
|
+
upper, middle, lower = SQAI.bbands(prices, period: 5)
|
|
167
210
|
|
|
168
|
-
|
|
211
|
+
# MACD
|
|
212
|
+
macd, signal, histogram = SQAI.macd(prices, fast_period: 12, slow_period: 26, signal_period: 9)
|
|
169
213
|
|
|
170
|
-
|
|
214
|
+
# Many more indicators available!
|
|
215
|
+
# See: https://github.com/MadBomber/sqa-tai
|
|
216
|
+
```
|
|
171
217
|
|
|
172
|
-
|
|
218
|
+
**Available Indicators:** SMA, EMA, WMA, RSI, MACD, Bollinger Bands, Stochastic, ADX, ATR, CCI, Williams %R, ROC, Momentum, and 140+ more via TA-Lib.
|
|
173
219
|
|
|
174
|
-
|
|
220
|
+
### Trading Strategies
|
|
175
221
|
|
|
176
|
-
|
|
222
|
+
Build and test trading strategies:
|
|
177
223
|
|
|
178
|
-
|
|
224
|
+
```ruby
|
|
225
|
+
require 'sqa'
|
|
226
|
+
require 'ostruct'
|
|
179
227
|
|
|
180
|
-
|
|
228
|
+
# Load strategies
|
|
229
|
+
strategies = SQA::Strategy.new
|
|
230
|
+
strategies.auto_load # Loads all built-in strategies
|
|
231
|
+
|
|
232
|
+
# Add specific strategies
|
|
233
|
+
strategies.add SQA::Strategy::RSI
|
|
234
|
+
strategies.add SQA::Strategy::SMA
|
|
235
|
+
strategies.add SQA::Strategy::EMA
|
|
236
|
+
|
|
237
|
+
# Prepare data for strategy execution
|
|
238
|
+
prices = aapl.df["adj_close_price"].to_a
|
|
239
|
+
rsi_value = SQAI.rsi(prices, period: 14).last
|
|
240
|
+
|
|
241
|
+
vector = OpenStruct.new
|
|
242
|
+
vector.rsi = rsi_value
|
|
243
|
+
vector.prices = prices
|
|
244
|
+
|
|
245
|
+
# Execute strategies
|
|
246
|
+
results = strategies.execute(vector)
|
|
247
|
+
#=> [:hold, :buy, :hold] # One result per strategy
|
|
248
|
+
|
|
249
|
+
# Built-in strategies:
|
|
250
|
+
# - SQA::Strategy::RSI - Based on Relative Strength Index
|
|
251
|
+
# - SQA::Strategy::SMA - Simple Moving Average crossover
|
|
252
|
+
# - SQA::Strategy::EMA - Exponential Moving Average crossover
|
|
253
|
+
# - SQA::Strategy::MACD - MACD crossover strategy
|
|
254
|
+
# - SQA::Strategy::BollingerBands - Bollinger Bands bounce strategy
|
|
255
|
+
# - SQA::Strategy::Stochastic - Stochastic oscillator strategy
|
|
256
|
+
# - SQA::Strategy::VolumeBreakout - Volume-based breakout strategy
|
|
257
|
+
# - SQA::Strategy::MR - Mean Reversion
|
|
258
|
+
# - SQA::Strategy::MP - Market Profile
|
|
259
|
+
# - SQA::Strategy::KBS - Knowledge-based RETE strategy (advanced)
|
|
260
|
+
# - SQA::Strategy::Random - Random signal generator (for testing)
|
|
261
|
+
# - SQA::Strategy::Consensus - Combines multiple strategies
|
|
262
|
+
```
|
|
181
263
|
|
|
182
|
-
|
|
264
|
+
### Statistics
|
|
183
265
|
|
|
184
|
-
|
|
266
|
+
Comprehensive statistical analysis on price data (via `lite-statistics` gem):
|
|
185
267
|
|
|
186
|
-
|
|
268
|
+
```ruby
|
|
269
|
+
require 'sqa'
|
|
187
270
|
|
|
188
|
-
|
|
271
|
+
prices = [179.8, 180.71, 178.85, 178.72, 177.15]
|
|
272
|
+
|
|
273
|
+
stats = prices.summary
|
|
274
|
+
#=> {
|
|
275
|
+
# max: 180.71,
|
|
276
|
+
# min: 177.15,
|
|
277
|
+
# mean: 179.046,
|
|
278
|
+
# median: 178.85,
|
|
279
|
+
# mode: nil,
|
|
280
|
+
# range: 3.56,
|
|
281
|
+
# sample_standard_deviation: 1.19,
|
|
282
|
+
# sample_variance: 1.42,
|
|
283
|
+
# ...
|
|
284
|
+
# }
|
|
285
|
+
```
|
|
189
286
|
|
|
287
|
+
### Portfolio Management
|
|
190
288
|
|
|
191
|
-
|
|
289
|
+
Track positions, trades, and P&L:
|
|
192
290
|
|
|
193
|
-
|
|
291
|
+
```ruby
|
|
292
|
+
require 'sqa'
|
|
194
293
|
|
|
294
|
+
# Create portfolio with initial cash and commission
|
|
295
|
+
portfolio = SQA::Portfolio.new(initial_cash: 10_000.0, commission: 1.0)
|
|
195
296
|
|
|
196
|
-
|
|
297
|
+
# Buy stock
|
|
298
|
+
portfolio.buy('AAPL', shares: 10, price: 150.0, date: Date.today)
|
|
197
299
|
|
|
198
|
-
|
|
300
|
+
# Sell stock
|
|
301
|
+
portfolio.sell('AAPL', shares: 5, price: 160.0, date: Date.today)
|
|
199
302
|
|
|
200
|
-
|
|
303
|
+
# Check portfolio value
|
|
304
|
+
current_prices = { 'AAPL' => 165.0 }
|
|
305
|
+
portfolio.value(current_prices)
|
|
306
|
+
#=> Total portfolio value
|
|
201
307
|
|
|
202
|
-
|
|
308
|
+
# Calculate profit/loss
|
|
309
|
+
portfolio.profit_loss(current_prices)
|
|
310
|
+
#=> P&L amount
|
|
203
311
|
|
|
204
|
-
|
|
312
|
+
# View summary
|
|
313
|
+
portfolio.summary
|
|
314
|
+
#=> { initial_cash: 10000.0, cash: 8248.0, positions: 1, trades: 2, ... }
|
|
205
315
|
|
|
206
|
-
|
|
316
|
+
# Save trades to CSV
|
|
317
|
+
portfolio.save_to_csv('my_trades.csv')
|
|
207
318
|
|
|
208
|
-
|
|
319
|
+
# Load from CSV
|
|
320
|
+
portfolio.load_from_csv('my_trades.csv')
|
|
321
|
+
```
|
|
209
322
|
|
|
210
|
-
|
|
323
|
+
### Backtesting
|
|
211
324
|
|
|
212
|
-
|
|
325
|
+
Simulate trading strategies against historical data:
|
|
213
326
|
|
|
214
327
|
```ruby
|
|
215
328
|
require 'sqa'
|
|
216
|
-
require 'sqa/cli'
|
|
217
329
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
330
|
+
SQA.init
|
|
331
|
+
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
332
|
+
|
|
333
|
+
# Create backtest
|
|
334
|
+
backtest = SQA::Backtest.new(
|
|
335
|
+
stock: stock,
|
|
336
|
+
strategy: SQA::Strategy::RSI,
|
|
337
|
+
initial_capital: 10_000.0,
|
|
338
|
+
commission: 1.0
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Run backtest
|
|
342
|
+
results = backtest.run
|
|
343
|
+
|
|
344
|
+
# View comprehensive metrics
|
|
345
|
+
puts "Total Return: #{results.total_return.round(2)}%"
|
|
346
|
+
puts "Annualized Return: #{results.annualized_return.round(2)}%"
|
|
347
|
+
puts "Sharpe Ratio: #{results.sharpe_ratio.round(2)}"
|
|
348
|
+
puts "Max Drawdown: #{results.max_drawdown.round(2)}%"
|
|
349
|
+
puts "Total Trades: #{results.total_trades}"
|
|
350
|
+
puts "Win Rate: #{results.win_rate.round(2)}%"
|
|
351
|
+
puts "Profit Factor: #{results.profit_factor.round(2)}"
|
|
352
|
+
|
|
353
|
+
# Access equity curve for charting
|
|
354
|
+
results.equity_curve #=> Array of portfolio values over time
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Strategy Generator
|
|
222
358
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
359
|
+
Reverse-engineer profitable trades to discover winning patterns:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
require 'sqa'
|
|
363
|
+
|
|
364
|
+
SQA.init
|
|
365
|
+
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
366
|
+
|
|
367
|
+
# Create strategy generator
|
|
368
|
+
generator = SQA::StrategyGenerator.new(
|
|
369
|
+
stock: stock,
|
|
370
|
+
min_gain_percent: 10.0, # Find trades with ≥10% gain
|
|
371
|
+
holding_period: (5..20) # Within 5-20 days
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Discover patterns
|
|
375
|
+
patterns = generator.discover_patterns(min_pattern_frequency: 3)
|
|
376
|
+
|
|
377
|
+
# Print discovered patterns
|
|
378
|
+
generator.print_patterns(max_patterns: 10)
|
|
379
|
+
#=> Pattern #1:
|
|
380
|
+
#=> Frequency: 15 occurrences
|
|
381
|
+
#=> Average Gain: 12.5%
|
|
382
|
+
#=> Average Holding: 8.3 days
|
|
383
|
+
#=> Conditions:
|
|
384
|
+
#=> - rsi: oversold
|
|
385
|
+
#=> - macd_crossover: bullish
|
|
386
|
+
#=> - volume: high
|
|
387
|
+
|
|
388
|
+
# Generate strategy from top pattern
|
|
389
|
+
strategy = generator.generate_strategy(pattern_index: 0, strategy_type: :class)
|
|
390
|
+
|
|
391
|
+
# Backtest the discovered strategy
|
|
392
|
+
backtest = SQA::Backtest.new(stock: stock, strategy: strategy)
|
|
393
|
+
results = backtest.run
|
|
394
|
+
|
|
395
|
+
# Export patterns to CSV
|
|
396
|
+
generator.export_patterns('/tmp/patterns.csv')
|
|
228
397
|
```
|
|
229
398
|
|
|
230
|
-
|
|
399
|
+
### Genetic Programming
|
|
400
|
+
|
|
401
|
+
Evolve optimal strategy parameters through natural selection:
|
|
231
402
|
|
|
232
403
|
```ruby
|
|
233
|
-
|
|
234
|
-
#=> [:timestamp, :open_price, :high_price, :low_price, :close_price, :adj_close_price, :volume]
|
|
404
|
+
require 'sqa'
|
|
235
405
|
|
|
236
|
-
|
|
237
|
-
|
|
406
|
+
SQA.init
|
|
407
|
+
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
408
|
+
|
|
409
|
+
# Create genetic program
|
|
410
|
+
gp = SQA::GeneticProgram.new(
|
|
411
|
+
stock: stock,
|
|
412
|
+
population_size: 50,
|
|
413
|
+
generations: 100,
|
|
414
|
+
mutation_rate: 0.15
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# Define parameter space to explore
|
|
418
|
+
gp.define_genes(
|
|
419
|
+
rsi_period: (7..30).to_a,
|
|
420
|
+
buy_threshold: (20..40).to_a,
|
|
421
|
+
sell_threshold: (60..80).to_a
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Define fitness function (backtest performance)
|
|
425
|
+
gp.fitness do |genes|
|
|
426
|
+
# Create strategy with these genes
|
|
427
|
+
strategy = create_rsi_strategy(genes)
|
|
428
|
+
|
|
429
|
+
# Backtest and return total return as fitness
|
|
430
|
+
backtest = SQA::Backtest.new(stock: stock, strategy: strategy)
|
|
431
|
+
backtest.run.total_return
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Evolve!
|
|
435
|
+
best = gp.evolve
|
|
436
|
+
|
|
437
|
+
puts "Best Parameters:"
|
|
438
|
+
puts " RSI Period: #{best.genes[:rsi_period]}"
|
|
439
|
+
puts " Buy Threshold: #{best.genes[:buy_threshold]}"
|
|
440
|
+
puts " Sell Threshold: #{best.genes[:sell_threshold]}"
|
|
441
|
+
puts " Fitness: #{best.fitness.round(2)}%"
|
|
442
|
+
|
|
443
|
+
# View evolution history
|
|
444
|
+
gp.history.each do |gen|
|
|
445
|
+
puts "Gen #{gen[:generation]}: Best=#{gen[:best_fitness].round(2)}%"
|
|
446
|
+
end
|
|
238
447
|
```
|
|
239
448
|
|
|
240
|
-
|
|
449
|
+
### Knowledge-Based Strategy (KBS)
|
|
450
|
+
|
|
451
|
+
Build sophisticated rule-based systems with RETE forward chaining:
|
|
241
452
|
|
|
242
453
|
```ruby
|
|
243
|
-
|
|
244
|
-
#=> [:ticker, :source, :indicators, :overview]
|
|
454
|
+
require 'sqa'
|
|
245
455
|
|
|
246
|
-
|
|
247
|
-
|
|
456
|
+
# Create KBS strategy
|
|
457
|
+
strategy = SQA::Strategy::KBS.new(load_defaults: false)
|
|
458
|
+
|
|
459
|
+
# Define custom trading rules
|
|
460
|
+
strategy.add_rule :golden_opportunity do
|
|
461
|
+
desc "Perfect storm: Multiple bullish indicators align"
|
|
462
|
+
|
|
463
|
+
# Multiple conditions
|
|
464
|
+
on :rsi, { level: :oversold }
|
|
465
|
+
on :macd, { crossover: :bullish }
|
|
466
|
+
on :stochastic, { zone: :oversold, crossover: :bullish }
|
|
467
|
+
on :trend, { short_term: :up, strength: :strong }
|
|
468
|
+
on :volume, { level: :high }
|
|
469
|
+
|
|
470
|
+
# Negation: Don't buy if overbought elsewhere
|
|
471
|
+
without :rsi, { level: :overbought }
|
|
472
|
+
|
|
473
|
+
# Action
|
|
474
|
+
then do
|
|
475
|
+
assert(:signal, { action: :buy, confidence: :high })
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Execute strategy
|
|
480
|
+
signal = strategy.execute(vector)
|
|
481
|
+
#=> :buy or :sell or :hold
|
|
482
|
+
|
|
483
|
+
# Use with backtesting
|
|
484
|
+
backtest = SQA::Backtest.new(
|
|
485
|
+
stock: stock,
|
|
486
|
+
strategy: SQA::Strategy::KBS,
|
|
487
|
+
initial_capital: 10_000.0
|
|
488
|
+
)
|
|
489
|
+
results = backtest.run
|
|
490
|
+
```
|
|
248
491
|
|
|
249
|
-
|
|
250
|
-
=> [:symbol, :asset_type, :name, :description, :cik, :exchange, :currency, :country, :sector, :industry, :address, :fiscal_year_end, :latest_quarter, :market_capitalization, :ebitda, :pe_ratio, :peg_ratio, :book_value, :dividend_per_share, :dividend_yield, :eps, :revenue_per_share_ttm, :profit_margin, :operating_margin_ttm, :return_on_assets_ttm, :return_on_equity_ttm, :revenue_ttm, :gross_profit_ttm, :diluted_epsttm, :quarterly_earnings_growth_yoy, :quarterly_revenue_growth_yoy, :analyst_target_price, :trailing_pe, :forward_pe, :price_to_sales_ratio_ttm, :price_to_book_ratio, :ev_to_revenue, :ev_to_ebitda, :beta, :"52_week_high", :"52_week_low", :"50_day_moving_average", :"200_day_moving_average", :shares_outstanding, :dividend_date, :ex_dividend_date]
|
|
492
|
+
### Real-Time Streaming
|
|
251
493
|
|
|
494
|
+
Process live stock prices and generate on-the-fly trading signals:
|
|
495
|
+
|
|
496
|
+
```ruby
|
|
497
|
+
require 'sqa'
|
|
498
|
+
|
|
499
|
+
# Create stream processor
|
|
500
|
+
stream = SQA::Stream.new(
|
|
501
|
+
ticker: 'AAPL',
|
|
502
|
+
window_size: 100,
|
|
503
|
+
strategies: [
|
|
504
|
+
SQA::Strategy::RSI,
|
|
505
|
+
SQA::Strategy::MACD,
|
|
506
|
+
SQA::Strategy::BollingerBands
|
|
507
|
+
]
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Register signal callback
|
|
511
|
+
stream.on_signal do |signal, data|
|
|
512
|
+
puts "🔔 SIGNAL: #{signal.upcase}"
|
|
513
|
+
puts " Price: $#{data[:price]}"
|
|
514
|
+
puts " Consensus: #{data[:strategies_vote]}"
|
|
515
|
+
|
|
516
|
+
# Execute trade, send alert, etc.
|
|
517
|
+
execute_trade(signal, data) if signal != :hold
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# Register update callback (optional)
|
|
521
|
+
stream.on_update do |data|
|
|
522
|
+
puts "📊 Price update: $#{data[:price]}"
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Feed live data (from WebSocket, API, etc.)
|
|
526
|
+
stream.update(
|
|
527
|
+
price: 150.25,
|
|
528
|
+
volume: 1_000_000,
|
|
529
|
+
timestamp: Time.now
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Access real-time indicators
|
|
533
|
+
rsi = stream.indicator(:rsi, period: 14)
|
|
534
|
+
sma = stream.indicator(:sma, period: 20)
|
|
535
|
+
|
|
536
|
+
# View stream statistics
|
|
537
|
+
stream.stats
|
|
538
|
+
#=> { ticker: 'AAPL', updates: 125, current_price: 150.25, ... }
|
|
252
539
|
```
|
|
253
540
|
|
|
254
|
-
###
|
|
541
|
+
### FPL Analysis (Future Period Loss/Profit)
|
|
255
542
|
|
|
256
|
-
|
|
543
|
+
Analyze potential future price movements with risk metrics:
|
|
257
544
|
|
|
258
545
|
```ruby
|
|
259
|
-
require '
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
:sample_standard_error => 0.532049621745943,
|
|
297
|
-
:sample_variance => 1.415384000000005,
|
|
298
|
-
:sample_zscores => {
|
|
299
|
-
179.8 => 0.6337736880639895,
|
|
300
|
-
180.71 => 1.3986729667618856,
|
|
301
|
-
178.85 => -0.16474753695031497,
|
|
302
|
-
178.72 => -0.2740188624785824,
|
|
303
|
-
177.15 => -1.5936802553969298
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
#=> nil
|
|
546
|
+
require 'sqa'
|
|
547
|
+
|
|
548
|
+
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
549
|
+
|
|
550
|
+
# Calculate FPL for 14-day future period
|
|
551
|
+
fpl_data = stock.df.fpl(fpop: 14)
|
|
552
|
+
# Returns: [[min_delta, max_delta], ...] for each price point
|
|
553
|
+
|
|
554
|
+
# Full analysis with risk metrics
|
|
555
|
+
analysis = stock.df.fpl_analysis(fpop: 14)
|
|
556
|
+
|
|
557
|
+
analysis.first
|
|
558
|
+
#=> {
|
|
559
|
+
# min_delta: -5.2, # Worst loss percentage
|
|
560
|
+
# max_delta: 8.7, # Best gain percentage
|
|
561
|
+
# risk: 13.9, # Volatility (max - min)
|
|
562
|
+
# direction: :UNCERTAIN,# :UP, :DOWN, :UNCERTAIN, :FLAT
|
|
563
|
+
# magnitude: 1.75, # Average expected movement
|
|
564
|
+
# interpretation: "UNCERTAIN: 1.75% (±6.95% risk)"
|
|
565
|
+
#}
|
|
566
|
+
|
|
567
|
+
# Filter high-quality opportunities
|
|
568
|
+
good_indices = SQA::FPOP.filter_by_quality(
|
|
569
|
+
analysis,
|
|
570
|
+
min_magnitude: 5.0, # Minimum average movement
|
|
571
|
+
max_risk: 10.0, # Maximum acceptable volatility
|
|
572
|
+
directions: [:UP] # Only bullish patterns
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# Use with Strategy Generator for risk-aware pattern discovery
|
|
576
|
+
generator = SQA::StrategyGenerator.new(
|
|
577
|
+
stock: stock,
|
|
578
|
+
min_gain_percent: 10.0,
|
|
579
|
+
fpop: 10,
|
|
580
|
+
max_fpl_risk: 20.0, # NEW: Filter high-volatility points
|
|
581
|
+
required_fpl_directions: [:UP] # NEW: Only bullish patterns
|
|
582
|
+
)
|
|
307
583
|
```
|
|
308
584
|
|
|
309
|
-
|
|
585
|
+
**Key Benefits:**
|
|
586
|
+
- Captures both upside AND downside potential
|
|
587
|
+
- Measures volatility/risk during future period
|
|
588
|
+
- Classifies directional bias (UP/DOWN/UNCERTAIN/FLAT)
|
|
589
|
+
- Enables risk-adjusted opportunity filtering
|
|
310
590
|
|
|
311
|
-
|
|
312
|
-
Here is an example:
|
|
591
|
+
### Market Regime Detection
|
|
313
592
|
|
|
593
|
+
Identify and adapt to changing market conditions:
|
|
314
594
|
|
|
315
595
|
```ruby
|
|
316
|
-
|
|
317
|
-
|
|
596
|
+
# Detect current market regime
|
|
597
|
+
regime = SQA::MarketRegime.detect(stock)
|
|
598
|
+
#=> {
|
|
599
|
+
# type: :bull, # :bull, :bear, :sideways
|
|
600
|
+
# volatility: :low, # :low, :medium, :high
|
|
601
|
+
# strength: :strong, # :weak, :moderate, :strong
|
|
602
|
+
# lookback_days: 60,
|
|
603
|
+
# detected_at: 2024-11-08
|
|
604
|
+
#}
|
|
605
|
+
|
|
606
|
+
# Analyze regime history
|
|
607
|
+
regimes = SQA::MarketRegime.detect_history(stock)
|
|
608
|
+
#=> [
|
|
609
|
+
# { type: :bull, start_index: 0, end_index: 120, duration: 120 },
|
|
610
|
+
# { type: :bear, start_index: 121, end_index: 200, duration: 79 },
|
|
611
|
+
# ...
|
|
612
|
+
#]
|
|
613
|
+
|
|
614
|
+
# Split data by regime
|
|
615
|
+
regime_data = SQA::MarketRegime.split_by_regime(stock)
|
|
616
|
+
#=> {
|
|
617
|
+
# bull: [{ prices: [...], start_index: 0, end_index: 120 }, ...],
|
|
618
|
+
# bear: [...],
|
|
619
|
+
# sideways: [...]
|
|
620
|
+
#}
|
|
621
|
+
|
|
622
|
+
# Use regime-specific strategies
|
|
623
|
+
current_regime = SQA::MarketRegime.detect(stock)
|
|
624
|
+
strategy = case current_regime[:type]
|
|
625
|
+
when :bull
|
|
626
|
+
SQA::Strategy::MomentumBreakout
|
|
627
|
+
when :bear
|
|
628
|
+
SQA::Strategy::MeanReversion
|
|
629
|
+
when :sideways
|
|
630
|
+
SQA::Strategy::BollingerBands
|
|
631
|
+
end
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Key Benefits:**
|
|
635
|
+
- Different strategies for different market conditions
|
|
636
|
+
- Avoid using bull market strategies in bear markets
|
|
637
|
+
- Detect regime changes before they impact performance
|
|
638
|
+
|
|
639
|
+
### Seasonal Analysis
|
|
318
640
|
|
|
319
|
-
|
|
320
|
-
|
|
641
|
+
Discover calendar-dependent patterns:
|
|
642
|
+
|
|
643
|
+
```ruby
|
|
644
|
+
# Analyze seasonal performance
|
|
645
|
+
seasonal = SQA::SeasonalAnalyzer.analyze(stock)
|
|
646
|
+
#=> {
|
|
647
|
+
# best_months: [10, 11, 12], # October, November, December
|
|
648
|
+
# worst_months: [5, 6, 7], # May, June, July
|
|
649
|
+
# best_quarters: [4, 1], # Q4 and Q1
|
|
650
|
+
# worst_quarters: [2, 3],
|
|
651
|
+
# has_seasonal_pattern: true,
|
|
652
|
+
# monthly_returns: { ... },
|
|
653
|
+
# quarterly_returns: { ... }
|
|
654
|
+
#}
|
|
655
|
+
|
|
656
|
+
# Filter data for Q4 only (holiday shopping season)
|
|
657
|
+
q4_data = SQA::SeasonalAnalyzer.filter_by_quarters(stock, [4])
|
|
658
|
+
#=> { indices: [...], dates: [...], prices: [...] }
|
|
659
|
+
|
|
660
|
+
# Filter for specific months
|
|
661
|
+
dec_data = SQA::SeasonalAnalyzer.filter_by_months(stock, [12])
|
|
662
|
+
|
|
663
|
+
# Get context for specific date
|
|
664
|
+
context = SQA::SeasonalAnalyzer.context_for_date(Date.new(2024, 12, 15))
|
|
665
|
+
#=> {
|
|
666
|
+
# month: 12,
|
|
667
|
+
# quarter: 4,
|
|
668
|
+
# month_name: "December",
|
|
669
|
+
# quarter_name: "Q4",
|
|
670
|
+
# is_year_end: true,
|
|
671
|
+
# is_holiday_season: true,
|
|
672
|
+
# is_earnings_season: false
|
|
673
|
+
#}
|
|
674
|
+
|
|
675
|
+
# Discover patterns only for best months
|
|
676
|
+
generator = SQA::StrategyGenerator.new(stock: stock)
|
|
677
|
+
patterns = generator.discover_context_aware_patterns(
|
|
678
|
+
analyze_seasonal: true
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
patterns.each do |pattern|
|
|
682
|
+
puts pattern.context.valid_months # [10, 11, 12] for Q4 patterns
|
|
683
|
+
end
|
|
321
684
|
```
|
|
322
685
|
|
|
323
|
-
|
|
686
|
+
**Real-World Examples:**
|
|
687
|
+
- Retail stocks: Q4 holiday shopping boost
|
|
688
|
+
- Tax prep stocks: Q1/Q4 seasonal surge
|
|
689
|
+
- Energy stocks: Summer driving season
|
|
690
|
+
- Agriculture stocks: Planting/harvest cycles
|
|
691
|
+
|
|
692
|
+
### Sector Analysis with KBS
|
|
324
693
|
|
|
325
|
-
|
|
694
|
+
Analyze patterns across related stocks using KBS blackboards:
|
|
326
695
|
|
|
327
696
|
```ruby
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
# that is defined in each strategy added
|
|
342
|
-
ap ss.strategies #=>∑
|
|
343
|
-
[
|
|
344
|
-
[0] SQA::Strategy::Random#trade(vector),
|
|
345
|
-
[1] SQA::Strategy::RSI#trade(vector)
|
|
697
|
+
# Create sector analyzer (separate blackboard per sector)
|
|
698
|
+
analyzer = SQA::SectorAnalyzer.new
|
|
699
|
+
|
|
700
|
+
# Add stocks to technology sector
|
|
701
|
+
analyzer.add_stock('AAPL', sector: :technology)
|
|
702
|
+
analyzer.add_stock('MSFT', sector: :technology)
|
|
703
|
+
analyzer.add_stock('GOOGL', sector: :technology)
|
|
704
|
+
|
|
705
|
+
# Detect sector-wide regime
|
|
706
|
+
tech_stocks = [
|
|
707
|
+
SQA::Stock.new(ticker: 'AAPL'),
|
|
708
|
+
SQA::Stock.new(ticker: 'MSFT'),
|
|
709
|
+
SQA::Stock.new(ticker: 'GOOGL')
|
|
346
710
|
]
|
|
347
711
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
712
|
+
sector_regime = analyzer.detect_sector_regime(:technology, tech_stocks)
|
|
713
|
+
#=> {
|
|
714
|
+
# sector: :technology,
|
|
715
|
+
# consensus_regime: :bull, # Majority vote
|
|
716
|
+
# sector_strength: 85.0, # % of stocks bullish
|
|
717
|
+
# stock_regimes: [...]
|
|
718
|
+
#}
|
|
719
|
+
|
|
720
|
+
# Discover patterns across entire sector
|
|
721
|
+
sector_patterns = analyzer.discover_sector_patterns(
|
|
722
|
+
:technology,
|
|
723
|
+
tech_stocks,
|
|
724
|
+
min_gain_percent: 10.0,
|
|
725
|
+
fpop: 10
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
# Patterns that work for MULTIPLE stocks in sector
|
|
729
|
+
sector_patterns.each do |sp|
|
|
730
|
+
puts "Pattern found in: #{sp[:stocks].join(', ')}"
|
|
731
|
+
puts "Avg gain: #{sp[:avg_gain].round(2)}%"
|
|
732
|
+
puts "Conditions: #{sp[:conditions]}"
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# Query sector blackboard
|
|
736
|
+
facts = analyzer.query_sector(:technology, :sector_pattern)
|
|
737
|
+
|
|
738
|
+
# Print sector summary
|
|
739
|
+
analyzer.print_sector_summary(:technology)
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
**Available Sectors:**
|
|
743
|
+
`:technology`, `:finance`, `:healthcare`, `:energy`, `:consumer`, `:industrial`, `:materials`, `:utilities`, `:real_estate`, `:communications`
|
|
744
|
+
|
|
745
|
+
**Key Benefits:**
|
|
746
|
+
- Leverage "stocks move together" in same sector
|
|
747
|
+
- Find patterns valid across multiple related stocks
|
|
748
|
+
- Persistent KBS blackboard tracks sector knowledge
|
|
749
|
+
- Cross-stock pattern validation
|
|
750
|
+
|
|
751
|
+
### Walk-Forward Validation
|
|
752
|
+
|
|
753
|
+
Prevent overfitting with time-series cross-validation:
|
|
754
|
+
|
|
755
|
+
```ruby
|
|
756
|
+
generator = SQA::StrategyGenerator.new(
|
|
757
|
+
stock: stock,
|
|
758
|
+
min_gain_percent: 10.0,
|
|
759
|
+
fpop: 10
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# Run walk-forward validation
|
|
763
|
+
results = generator.walk_forward_validate(
|
|
764
|
+
train_size: 250, # 1 year training window
|
|
765
|
+
test_size: 60, # 3 months testing window
|
|
766
|
+
step_size: 30 # Step forward 1 month each iteration
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
# Only patterns that work out-of-sample
|
|
770
|
+
validated_patterns = results[:validated_patterns]
|
|
771
|
+
|
|
772
|
+
# Detailed results for each iteration
|
|
773
|
+
results[:validation_results].each do |r|
|
|
774
|
+
puts "Train: #{r[:train_period]}"
|
|
775
|
+
puts "Test: #{r[:test_period]}"
|
|
776
|
+
puts "Return: #{r[:test_return]}%"
|
|
777
|
+
puts "Sharpe: #{r[:test_sharpe]}"
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
# Statistics
|
|
781
|
+
all_returns = results[:validation_results].map { |r| r[:test_return] }
|
|
782
|
+
avg_return = all_returns.sum / all_returns.size
|
|
783
|
+
puts "Average out-of-sample return: #{avg_return.round(2)}%"
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**How It Works:**
|
|
787
|
+
1. Split data into train/test windows
|
|
788
|
+
2. Discover patterns on training data
|
|
789
|
+
3. Test patterns on future (unseen) data
|
|
790
|
+
4. Roll forward and repeat
|
|
791
|
+
5. Only keep patterns that work out-of-sample
|
|
792
|
+
|
|
793
|
+
**Prevents:**
|
|
794
|
+
- Curve-fitting to historical noise
|
|
795
|
+
- Patterns that only worked in the past
|
|
796
|
+
- Overconfidence in backtest results
|
|
797
|
+
|
|
798
|
+
### Pattern Context System
|
|
799
|
+
|
|
800
|
+
Context-aware patterns that know when/where they're valid:
|
|
801
|
+
|
|
802
|
+
```ruby
|
|
803
|
+
# Discover patterns with full context
|
|
804
|
+
generator = SQA::StrategyGenerator.new(stock: stock, min_gain_percent: 10.0)
|
|
805
|
+
|
|
806
|
+
patterns = generator.discover_context_aware_patterns(
|
|
807
|
+
analyze_regime: true,
|
|
808
|
+
analyze_seasonal: true,
|
|
809
|
+
sector: :technology
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
# Each pattern has rich context metadata
|
|
813
|
+
pattern = patterns.first
|
|
814
|
+
pattern.context.market_regime #=> :bull
|
|
815
|
+
pattern.context.volatility_regime #=> :low
|
|
816
|
+
pattern.context.valid_months #=> [10, 11, 12]
|
|
817
|
+
pattern.context.valid_quarters #=> [4, 1]
|
|
818
|
+
pattern.context.sector #=> :technology
|
|
819
|
+
pattern.context.discovered_period #=> "2020-01-01 to 2022-12-31"
|
|
820
|
+
|
|
821
|
+
# Runtime validation - check if pattern applies NOW
|
|
822
|
+
valid = pattern.context.valid_for?(
|
|
823
|
+
date: Date.today,
|
|
824
|
+
regime: :bull,
|
|
825
|
+
sector: :technology
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
if valid
|
|
829
|
+
# Execute strategy - context matches current conditions
|
|
830
|
+
signal = strategy.trade(vector)
|
|
831
|
+
else
|
|
832
|
+
# Skip - pattern not valid in current context
|
|
833
|
+
puts "Pattern not valid for current conditions"
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
# Pattern summary includes context
|
|
837
|
+
puts pattern.to_s
|
|
838
|
+
#=> "Pattern(conditions=3, freq=12, gain=11.5%, success=75.0%) [bull months:10,11,12 technology]"
|
|
351
839
|
```
|
|
352
840
|
|
|
353
|
-
|
|
841
|
+
**Context Metadata:**
|
|
842
|
+
- **Market regime**: Bull/bear/sideways classification
|
|
843
|
+
- **Seasonality**: Valid months/quarters
|
|
844
|
+
- **Sector**: Industry classification
|
|
845
|
+
- **Volatility regime**: Low/medium/high
|
|
846
|
+
- **Discovery period**: When pattern was found
|
|
847
|
+
- **Stability score**: Consistency over time
|
|
354
848
|
|
|
355
|
-
|
|
849
|
+
**Key Benefits:**
|
|
850
|
+
- Patterns aren't universal - they're context-specific
|
|
851
|
+
- Avoid using patterns in wrong market conditions
|
|
852
|
+
- Seasonal awareness (Q4 retail patterns, etc.)
|
|
853
|
+
- Sector-specific strategies
|
|
854
|
+
- Runtime validation before execution
|
|
356
855
|
|
|
357
|
-
|
|
856
|
+
## Interactive Console
|
|
358
857
|
|
|
359
|
-
|
|
858
|
+
Launch an interactive Ruby console with SQA loaded:
|
|
360
859
|
|
|
361
|
-
|
|
860
|
+
```bash
|
|
861
|
+
sqa-console
|
|
862
|
+
```
|
|
362
863
|
|
|
363
|
-
|
|
864
|
+
This opens IRB with the SQA library pre-loaded, allowing you to experiment interactively:
|
|
364
865
|
|
|
365
|
-
|
|
866
|
+
```ruby
|
|
867
|
+
# Already loaded: require 'sqa'
|
|
366
868
|
|
|
367
|
-
|
|
869
|
+
SQA.init
|
|
870
|
+
stock = SQA::Stock.new(ticker: 'MSFT')
|
|
871
|
+
prices = stock.df["close_price"].to_a
|
|
872
|
+
sma = SQAI.sma(prices, period: 20)
|
|
873
|
+
```
|
|
368
874
|
|
|
369
|
-
|
|
875
|
+
## Examples
|
|
370
876
|
|
|
371
|
-
|
|
877
|
+
The `examples/` directory contains comprehensive demonstrations of advanced features:
|
|
372
878
|
|
|
373
|
-
|
|
879
|
+
- **`genetic_programming_example.rb`** - Evolve RSI parameters through natural selection
|
|
880
|
+
- **`kbs_strategy_example.rb`** - Build rule-based trading systems with RETE
|
|
881
|
+
- **`realtime_stream_example.rb`** - Process live price streams with callbacks
|
|
882
|
+
- **`strategy_generator_example.rb`** - Mine profitable patterns from history
|
|
374
883
|
|
|
375
|
-
|
|
884
|
+
Run examples:
|
|
885
|
+
```bash
|
|
886
|
+
ruby examples/genetic_programming_example.rb
|
|
887
|
+
ruby examples/strategy_generator_example.rb
|
|
888
|
+
```
|
|
376
889
|
|
|
377
|
-
|
|
890
|
+
See `examples/README.md` for detailed documentation and integration patterns.
|
|
378
891
|
|
|
379
|
-
##
|
|
892
|
+
## Architecture
|
|
380
893
|
|
|
381
|
-
|
|
894
|
+
**Core Components:**
|
|
382
895
|
|
|
383
|
-
|
|
896
|
+
- **`SQA::DataFrame`** - High-performance Polars-based data container for time series
|
|
897
|
+
- **`SQA::Stock`** - Represents a stock with price history and metadata
|
|
898
|
+
- **`SQA::Ticker`** - Stock symbol validation and lookup
|
|
899
|
+
- **`SQA::Strategy`** - Trading strategy execution framework
|
|
900
|
+
- **`SQA::Config`** - Configuration management
|
|
901
|
+
- **`SQAI`** - Alias for `SQA::TAI` (technical indicators from sqa-tai gem)
|
|
384
902
|
|
|
385
|
-
|
|
903
|
+
**Advanced Components:**
|
|
386
904
|
|
|
387
|
-
|
|
905
|
+
- **`SQA::Portfolio`** - Position and trade tracking with P&L calculations
|
|
906
|
+
- **`SQA::Backtest`** - Strategy simulation with comprehensive metrics
|
|
907
|
+
- **`SQA::StrategyGenerator`** - Pattern mining from profitable historical trades
|
|
908
|
+
- **`SQA::GeneticProgram`** - Evolutionary algorithm for parameter optimization
|
|
909
|
+
- **`SQA::Strategy::KBS`** - RETE-based forward-chaining inference engine
|
|
910
|
+
- **`SQA::Stream`** - Real-time price stream processor with event callbacks
|
|
388
911
|
|
|
389
|
-
|
|
912
|
+
**Data Flow:**
|
|
390
913
|
|
|
391
|
-
|
|
914
|
+
1. Create `SQA::Stock` with ticker symbol
|
|
915
|
+
2. Stock fetches data from Alpha Vantage or Yahoo Finance
|
|
916
|
+
3. Data stored in Polars-based `SQA::DataFrame`
|
|
917
|
+
4. Apply technical indicators via `SQAI` / `SQA::TAI`
|
|
918
|
+
5. Execute trading strategies to generate signals
|
|
919
|
+
6. Analyze results with statistical functions
|
|
392
920
|
|
|
393
|
-
|
|
921
|
+
**Design Patterns:**
|
|
394
922
|
|
|
395
|
-
|
|
923
|
+
- Plugin architecture for indicators and strategies
|
|
924
|
+
- Data source abstraction (Alpha Vantage, Yahoo Finance)
|
|
925
|
+
- Delegation to Polars for DataFrame operations
|
|
926
|
+
- Configuration hierarchy: defaults < environment variables < config file
|
|
396
927
|
|
|
928
|
+
## Data Sources
|
|
397
929
|
|
|
398
|
-
|
|
930
|
+
### Alpha Vantage
|
|
399
931
|
|
|
400
|
-
|
|
932
|
+
**Recommended data source** with a well-documented API.
|
|
401
933
|
|
|
402
|
-
|
|
934
|
+
- **URL:** [https://www.alphavantage.co/](https://www.alphavantage.co/)
|
|
935
|
+
- **API Key:** Required (free tier available)
|
|
936
|
+
- **Environment Variable:** `AV_API_KEY` or `ALPHAVANTAGE_API_KEY`
|
|
937
|
+
- **Rate Limits:** 5 calls/minute, 100 calls/day (free tier)
|
|
403
938
|
|
|
404
|
-
|
|
939
|
+
```ruby
|
|
940
|
+
stock = SQA::Stock.new(ticker: 'GOOGL', source: :alpha_vantage)
|
|
941
|
+
```
|
|
405
942
|
|
|
406
|
-
|
|
943
|
+
### Yahoo Finance
|
|
407
944
|
|
|
408
|
-
|
|
945
|
+
**No API available** - uses web scraping for historical data.
|
|
409
946
|
|
|
410
|
-
|
|
947
|
+
- **URL:** [https://finance.yahoo.com/](https://finance.yahoo.com/)
|
|
948
|
+
- **Manual Download:** Download CSV files and place in `SQA.data_dir`
|
|
949
|
+
- **Filename Format:** `ticker.csv` (lowercase), e.g., `aapl.csv`
|
|
411
950
|
|
|
412
|
-
|
|
951
|
+
To manually download:
|
|
952
|
+
1. Visit [https://finance.yahoo.com/quote/AAPL/history?p=AAPL](https://finance.yahoo.com/quote/AAPL/history?p=AAPL)
|
|
953
|
+
2. Download historical data as CSV
|
|
954
|
+
3. Move to your data directory as `aapl.csv`
|
|
413
955
|
|
|
956
|
+
```ruby
|
|
957
|
+
stock = SQA::Stock.new(ticker: 'AAPL', source: :yahoo_finance)
|
|
958
|
+
```
|
|
414
959
|
|
|
415
960
|
## Contributing
|
|
416
961
|
|
|
417
|
-
|
|
962
|
+
Contributions are welcome! Got an idea for a new indicator or strategy? Want to improve the math or signals?
|
|
418
963
|
|
|
419
|
-
Bug reports and pull requests
|
|
964
|
+
- **Bug reports and pull requests:** [https://github.com/MadBomber/sqa](https://github.com/MadBomber/sqa)
|
|
965
|
+
- **Technical indicators:** Contribute to [sqa-tai](https://github.com/MadBomber/sqa-tai)
|
|
420
966
|
|
|
421
967
|
## License
|
|
422
968
|
|
|
423
969
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
970
|
+
|
|
971
|
+
---
|
|
972
|
+
|
|
973
|
+
**Remember:** This is an educational tool. Historical performance is not an indicator of future results. Never use this for real trading decisions.
|