sqa 0.0.22 → 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 +86 -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 +833 -213
- 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/checksums/sqa-0.0.23.gem.sha512 +1 -0
- data/checksums/sqa-0.0.24.gem.sha512 +1 -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/ta_lib.md +160 -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/config.rb +22 -9
- 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 +34 -41
- 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 +16 -9
- 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/plugin_manager.rb +20 -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 -96
- data/lib/sqa/strategy/bollinger_bands.rb +42 -0
- data/lib/sqa/strategy/common.rb +0 -2
- 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 +41 -14
- data/main.just +81 -0
- data/mkdocs.yml +288 -0
- data/trace.log +0 -0
- metadata +279 -48
- data/bin/sqa +0 -6
- data/lib/sqa/activity.rb +0 -10
- data/lib/sqa/analysis.rb +0 -306
- data/lib/sqa/cli.rb +0 -173
- data/lib/sqa/constants.rb +0 -23
- data/lib/sqa/indicator/average_true_range.rb +0 -43
- 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/web.rb +0 -159
data/README.md
CHANGED
|
@@ -1,353 +1,973 @@
|
|
|
1
1
|
# SQA - Simple Qualitative Analysis
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.youtube.com/watch?v=VqomZQMZQCQ)
|
|
4
4
|
|
|
5
|
-
|
|
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**.
|
|
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.
|
|
6
8
|
|
|
7
9
|
<!-- Tocer[start]: Auto-generated, don't remove. -->
|
|
8
10
|
|
|
9
11
|
## Table of Contents
|
|
10
12
|
|
|
11
|
-
- [
|
|
13
|
+
- [Features](#features)
|
|
12
14
|
- [Installation](#installation)
|
|
13
|
-
- [
|
|
15
|
+
- [Configuration](#configuration)
|
|
16
|
+
- [Data Directory](#data-directory)
|
|
17
|
+
- [Alpha Vantage API Key](#alpha-vantage-api-key)
|
|
14
18
|
- [Usage](#usage)
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
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)
|
|
18
28
|
- [Yahoo Finance](#yahoo-finance)
|
|
19
|
-
- [Playing in IRB](#playing-in-irb)
|
|
20
|
-
- [With Stocks and Indicators](#with-stocks-and-indicators)
|
|
21
|
-
- [Get Historical Prices](#get-historical-prices)
|
|
22
|
-
- [Playing in the IRB - Setup](#playing-in-the-irb---setup)
|
|
23
|
-
- [Playing in the IRB - Statistics](#playing-in-the-irb---statistics)
|
|
24
|
-
- [Playing in the IRB - Indicators](#playing-in-the-irb---indicators)
|
|
25
|
-
- [Playing in the IRB - Strategies](#playing-in-the-irb---strategies)
|
|
26
|
-
- [Included Program Examples](#included-program-examples)
|
|
27
|
-
- [Analysis](#analysis)
|
|
28
|
-
- [Web](#web)
|
|
29
29
|
- [Contributing](#contributing)
|
|
30
30
|
- [License](#license)
|
|
31
31
|
|
|
32
32
|
<!-- Tocer[finish]: Auto-generated, don't remove. -->
|
|
33
33
|
|
|
34
|
-
##
|
|
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
|
|
35
59
|
|
|
36
|
-
|
|
60
|
+
## Installation
|
|
37
61
|
|
|
62
|
+
Install the gem:
|
|
38
63
|
|
|
39
|
-
|
|
64
|
+
```bash
|
|
65
|
+
gem install sqa
|
|
66
|
+
```
|
|
40
67
|
|
|
41
|
-
|
|
68
|
+
Or add to your `Gemfile`:
|
|
42
69
|
|
|
43
|
-
|
|
70
|
+
```ruby
|
|
71
|
+
gem 'sqa'
|
|
72
|
+
```
|
|
44
73
|
|
|
45
|
-
|
|
74
|
+
Then run:
|
|
46
75
|
|
|
47
|
-
|
|
76
|
+
```bash
|
|
77
|
+
bundle install
|
|
78
|
+
```
|
|
48
79
|
|
|
49
|
-
##
|
|
80
|
+
## Configuration
|
|
50
81
|
|
|
51
|
-
|
|
82
|
+
### Data Directory
|
|
83
|
+
|
|
84
|
+
SQA stores historical price data and metadata in a data directory. By default, it uses `~/sqa_data/`:
|
|
52
85
|
|
|
53
86
|
```ruby
|
|
54
|
-
|
|
55
|
-
sqa --version
|
|
87
|
+
require 'sqa'
|
|
56
88
|
|
|
57
|
-
#
|
|
89
|
+
# Initialize with default configuration
|
|
90
|
+
SQA.init
|
|
91
|
+
|
|
92
|
+
# Or specify a custom data directory
|
|
93
|
+
SQA::Config.new(data_dir: '~/Documents/my_stock_data')
|
|
94
|
+
```
|
|
58
95
|
|
|
59
|
-
|
|
60
|
-
exit(1) unless SQA.version >= SemVersion("1.0.0")
|
|
96
|
+
Create your data directory:
|
|
61
97
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# dependencies for you.
|
|
98
|
+
```bash
|
|
99
|
+
mkdir ~/sqa_data
|
|
65
100
|
```
|
|
66
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
|
+
|
|
67
118
|
## Usage
|
|
68
119
|
|
|
69
|
-
|
|
120
|
+
### Working with Stocks
|
|
70
121
|
|
|
71
|
-
|
|
122
|
+
```ruby
|
|
123
|
+
require 'sqa'
|
|
124
|
+
|
|
125
|
+
# Initialize SQA
|
|
126
|
+
SQA.init
|
|
72
127
|
|
|
73
|
-
|
|
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
|
|
74
131
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
132
|
+
# Access the DataFrame
|
|
133
|
+
aapl.df
|
|
134
|
+
#=> SQA::DataFrame (wraps Polars::DataFrame)
|
|
78
135
|
|
|
79
|
-
|
|
136
|
+
# Get column names
|
|
137
|
+
aapl.df.columns
|
|
138
|
+
#=> ["timestamp", "open_price", "high_price", "low_price", "close_price", "adj_close_price", "volume"]
|
|
80
139
|
|
|
81
|
-
|
|
140
|
+
# Access price data
|
|
141
|
+
aapl.df["adj_close_price"]
|
|
142
|
+
#=> Polars::Series with all adjusted closing prices
|
|
82
143
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
--dump-config path_to_file Dump the current configuration
|
|
88
|
-
-h, --help Print usage
|
|
89
|
-
-l, --log_level string Set the log level (debug, info, warn, error,
|
|
90
|
-
fatal)
|
|
91
|
-
-p, --portfolio string Set the filename of the portfolio
|
|
92
|
-
-t, --trades string Set the filename into which trades are
|
|
93
|
-
stored
|
|
94
|
-
-v, --verbose Print verbosely
|
|
95
|
-
--version Print version
|
|
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]
|
|
96
148
|
|
|
97
|
-
|
|
98
|
-
|
|
149
|
+
# Access company metadata
|
|
150
|
+
aapl.name
|
|
151
|
+
#=> "Apple Inc."
|
|
99
152
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
web - Run a web server
|
|
153
|
+
aapl.exchange
|
|
154
|
+
#=> "NASDAQ"
|
|
103
155
|
|
|
104
|
-
|
|
156
|
+
aapl.overview
|
|
157
|
+
#=> Hash with detailed company information
|
|
105
158
|
```
|
|
106
159
|
|
|
107
|
-
|
|
160
|
+
### DataFrame Operations
|
|
161
|
+
|
|
162
|
+
SQA::DataFrame is a wrapper around the high-performance Polars DataFrame:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
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)
|
|
173
|
+
|
|
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
|
+
```
|
|
108
186
|
|
|
109
|
-
###
|
|
187
|
+
### Technical Indicators
|
|
110
188
|
|
|
111
|
-
|
|
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:
|
|
112
190
|
|
|
113
191
|
```ruby
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
192
|
+
require 'sqa'
|
|
193
|
+
|
|
194
|
+
prices = [100, 102, 105, 103, 107, 110, 108, 112, 115, 113]
|
|
195
|
+
|
|
196
|
+
# Simple Moving Average
|
|
197
|
+
sma = SQAI.sma(prices, period: 5)
|
|
198
|
+
#=> [104.0, 105.4, 106.6, 108.0, 110.4, 111.6]
|
|
199
|
+
|
|
200
|
+
# Relative Strength Index
|
|
201
|
+
rsi = SQAI.rsi(prices, period: 14)
|
|
202
|
+
#=> [70.5, 68.2, ...]
|
|
203
|
+
|
|
204
|
+
# Exponential Moving Average
|
|
205
|
+
ema = SQAI.ema(prices, period: 5)
|
|
206
|
+
#=> [...]
|
|
207
|
+
|
|
208
|
+
# Bollinger Bands (returns upper, middle, lower)
|
|
209
|
+
upper, middle, lower = SQAI.bbands(prices, period: 5)
|
|
210
|
+
|
|
211
|
+
# MACD
|
|
212
|
+
macd, signal, histogram = SQAI.macd(prices, fast_period: 12, slow_period: 26, signal_period: 9)
|
|
213
|
+
|
|
214
|
+
# Many more indicators available!
|
|
215
|
+
# See: https://github.com/MadBomber/sqa-tai
|
|
117
216
|
```
|
|
118
217
|
|
|
119
|
-
|
|
218
|
+
**Available Indicators:** SMA, EMA, WMA, RSI, MACD, Bollinger Bands, Stochastic, ADX, ATR, CCI, Williams %R, ROC, Momentum, and 140+ more via TA-Lib.
|
|
120
219
|
|
|
121
|
-
|
|
220
|
+
### Trading Strategies
|
|
122
221
|
|
|
123
|
-
|
|
222
|
+
Build and test trading strategies:
|
|
124
223
|
|
|
125
|
-
|
|
224
|
+
```ruby
|
|
225
|
+
require 'sqa'
|
|
226
|
+
require 'ostruct'
|
|
126
227
|
|
|
127
|
-
|
|
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
|
+
```
|
|
128
263
|
|
|
129
|
-
|
|
264
|
+
### Statistics
|
|
130
265
|
|
|
131
|
-
|
|
266
|
+
Comprehensive statistical analysis on price data (via `lite-statistics` gem):
|
|
132
267
|
|
|
133
|
-
|
|
268
|
+
```ruby
|
|
269
|
+
require 'sqa'
|
|
134
270
|
|
|
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
|
+
```
|
|
135
286
|
|
|
136
|
-
###
|
|
287
|
+
### Portfolio Management
|
|
137
288
|
|
|
138
|
-
|
|
289
|
+
Track positions, trades, and P&L:
|
|
139
290
|
|
|
140
|
-
|
|
291
|
+
```ruby
|
|
292
|
+
require 'sqa'
|
|
141
293
|
|
|
142
|
-
|
|
294
|
+
# Create portfolio with initial cash and commission
|
|
295
|
+
portfolio = SQA::Portfolio.new(initial_cash: 10_000.0, commission: 1.0)
|
|
143
296
|
|
|
144
|
-
|
|
297
|
+
# Buy stock
|
|
298
|
+
portfolio.buy('AAPL', shares: 10, price: 150.0, date: Date.today)
|
|
145
299
|
|
|
146
|
-
|
|
300
|
+
# Sell stock
|
|
301
|
+
portfolio.sell('AAPL', shares: 5, price: 160.0, date: Date.today)
|
|
147
302
|
|
|
148
|
-
|
|
303
|
+
# Check portfolio value
|
|
304
|
+
current_prices = { 'AAPL' => 165.0 }
|
|
305
|
+
portfolio.value(current_prices)
|
|
306
|
+
#=> Total portfolio value
|
|
149
307
|
|
|
150
|
-
|
|
308
|
+
# Calculate profit/loss
|
|
309
|
+
portfolio.profit_loss(current_prices)
|
|
310
|
+
#=> P&L amount
|
|
151
311
|
|
|
152
|
-
|
|
312
|
+
# View summary
|
|
313
|
+
portfolio.summary
|
|
314
|
+
#=> { initial_cash: 10000.0, cash: 8248.0, positions: 1, trades: 2, ... }
|
|
153
315
|
|
|
154
|
-
|
|
316
|
+
# Save trades to CSV
|
|
317
|
+
portfolio.save_to_csv('my_trades.csv')
|
|
155
318
|
|
|
156
|
-
|
|
319
|
+
# Load from CSV
|
|
320
|
+
portfolio.load_from_csv('my_trades.csv')
|
|
321
|
+
```
|
|
157
322
|
|
|
323
|
+
### Backtesting
|
|
158
324
|
|
|
159
|
-
|
|
325
|
+
Simulate trading strategies against historical data:
|
|
160
326
|
|
|
161
|
-
|
|
327
|
+
```ruby
|
|
328
|
+
require 'sqa'
|
|
162
329
|
|
|
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
|
+
```
|
|
163
356
|
|
|
164
|
-
###
|
|
357
|
+
### Strategy Generator
|
|
165
358
|
|
|
166
|
-
|
|
359
|
+
Reverse-engineer profitable trades to discover winning patterns:
|
|
167
360
|
|
|
168
|
-
|
|
361
|
+
```ruby
|
|
362
|
+
require 'sqa'
|
|
169
363
|
|
|
170
|
-
|
|
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')
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Genetic Programming
|
|
171
400
|
|
|
172
|
-
|
|
401
|
+
Evolve optimal strategy parameters through natural selection:
|
|
173
402
|
|
|
174
|
-
|
|
403
|
+
```ruby
|
|
404
|
+
require 'sqa'
|
|
175
405
|
|
|
176
|
-
|
|
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
|
|
447
|
+
```
|
|
177
448
|
|
|
178
|
-
|
|
449
|
+
### Knowledge-Based Strategy (KBS)
|
|
179
450
|
|
|
180
|
-
|
|
451
|
+
Build sophisticated rule-based systems with RETE forward chaining:
|
|
181
452
|
|
|
182
453
|
```ruby
|
|
183
454
|
require 'sqa'
|
|
184
|
-
require 'sqa/cli'
|
|
185
455
|
|
|
186
|
-
#
|
|
187
|
-
SQA.
|
|
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
|
+
```
|
|
491
|
+
|
|
492
|
+
### Real-Time Streaming
|
|
188
493
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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, ... }
|
|
194
539
|
```
|
|
195
540
|
|
|
196
|
-
|
|
541
|
+
### FPL Analysis (Future Period Loss/Profit)
|
|
542
|
+
|
|
543
|
+
Analyze potential future price movements with risk metrics:
|
|
197
544
|
|
|
198
545
|
```ruby
|
|
199
|
-
|
|
200
|
-
#=> [:timestamp, :open_price, :high_price, :low_price, :close_price, :adj_close_price, :volume]
|
|
546
|
+
require 'sqa'
|
|
201
547
|
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
)
|
|
204
583
|
```
|
|
205
584
|
|
|
206
|
-
|
|
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
|
|
590
|
+
|
|
591
|
+
### Market Regime Detection
|
|
592
|
+
|
|
593
|
+
Identify and adapt to changing market conditions:
|
|
207
594
|
|
|
208
595
|
```ruby
|
|
209
|
-
|
|
210
|
-
|
|
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
|
|
211
638
|
|
|
212
|
-
|
|
213
|
-
=> "alpha_vantage"
|
|
639
|
+
### Seasonal Analysis
|
|
214
640
|
|
|
215
|
-
|
|
216
|
-
=> [: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]
|
|
641
|
+
Discover calendar-dependent patterns:
|
|
217
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
|
|
218
684
|
```
|
|
219
685
|
|
|
220
|
-
|
|
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
|
|
221
693
|
|
|
222
|
-
|
|
694
|
+
Analyze patterns across related stocks using KBS blackboards:
|
|
223
695
|
|
|
224
696
|
```ruby
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
178.72 => -0.2740188624785824,
|
|
269
|
-
177.15 => -1.5936802553969298
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
#=> nil
|
|
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')
|
|
710
|
+
]
|
|
711
|
+
|
|
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)
|
|
273
740
|
```
|
|
274
741
|
|
|
275
|
-
|
|
742
|
+
**Available Sectors:**
|
|
743
|
+
`:technology`, `:finance`, `:healthcare`, `:energy`, `:consumer`, `:industrial`, `:materials`, `:utilities`, `:real_estate`, `:communications`
|
|
276
744
|
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
279
750
|
|
|
751
|
+
### Walk-Forward Validation
|
|
752
|
+
|
|
753
|
+
Prevent overfitting with time-series cross-validation:
|
|
280
754
|
|
|
281
755
|
```ruby
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
284
799
|
|
|
285
|
-
|
|
286
|
-
|
|
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]"
|
|
287
839
|
```
|
|
288
840
|
|
|
289
|
-
|
|
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
|
|
848
|
+
|
|
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
|
|
855
|
+
|
|
856
|
+
## Interactive Console
|
|
857
|
+
|
|
858
|
+
Launch an interactive Ruby console with SQA loaded:
|
|
859
|
+
|
|
860
|
+
```bash
|
|
861
|
+
sqa-console
|
|
862
|
+
```
|
|
290
863
|
|
|
291
|
-
|
|
864
|
+
This opens IRB with the SQA library pre-loaded, allowing you to experiment interactively:
|
|
292
865
|
|
|
293
866
|
```ruby
|
|
294
|
-
require '
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
[0] SQA::Strategy::Random#trade(vector),
|
|
311
|
-
[1] SQA::Strategy::RSI#trade(vector)
|
|
312
|
-
]
|
|
867
|
+
# Already loaded: require 'sqa'
|
|
868
|
+
|
|
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
|
+
```
|
|
874
|
+
|
|
875
|
+
## Examples
|
|
876
|
+
|
|
877
|
+
The `examples/` directory contains comprehensive demonstrations of advanced features:
|
|
878
|
+
|
|
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
|
|
313
883
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
884
|
+
Run examples:
|
|
885
|
+
```bash
|
|
886
|
+
ruby examples/genetic_programming_example.rb
|
|
887
|
+
ruby examples/strategy_generator_example.rb
|
|
317
888
|
```
|
|
318
889
|
|
|
319
|
-
`
|
|
890
|
+
See `examples/README.md` for detailed documentation and integration patterns.
|
|
320
891
|
|
|
321
|
-
|
|
892
|
+
## Architecture
|
|
322
893
|
|
|
323
|
-
|
|
894
|
+
**Core Components:**
|
|
324
895
|
|
|
325
|
-
|
|
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)
|
|
326
902
|
|
|
327
|
-
|
|
903
|
+
**Advanced Components:**
|
|
328
904
|
|
|
329
|
-
|
|
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
|
|
330
911
|
|
|
331
|
-
|
|
912
|
+
**Data Flow:**
|
|
332
913
|
|
|
333
|
-
|
|
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
|
|
334
920
|
|
|
335
|
-
|
|
921
|
+
**Design Patterns:**
|
|
336
922
|
|
|
337
|
-
|
|
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
|
|
338
927
|
|
|
339
|
-
|
|
928
|
+
## Data Sources
|
|
340
929
|
|
|
341
|
-
###
|
|
930
|
+
### Alpha Vantage
|
|
342
931
|
|
|
343
|
-
|
|
932
|
+
**Recommended data source** with a well-documented API.
|
|
933
|
+
|
|
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)
|
|
938
|
+
|
|
939
|
+
```ruby
|
|
940
|
+
stock = SQA::Stock.new(ticker: 'GOOGL', source: :alpha_vantage)
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
### Yahoo Finance
|
|
944
|
+
|
|
945
|
+
**No API available** - uses web scraping for historical data.
|
|
946
|
+
|
|
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`
|
|
950
|
+
|
|
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`
|
|
955
|
+
|
|
956
|
+
```ruby
|
|
957
|
+
stock = SQA::Stock.new(ticker: 'AAPL', source: :yahoo_finance)
|
|
958
|
+
```
|
|
344
959
|
|
|
345
960
|
## Contributing
|
|
346
961
|
|
|
347
|
-
|
|
962
|
+
Contributions are welcome! Got an idea for a new indicator or strategy? Want to improve the math or signals?
|
|
348
963
|
|
|
349
|
-
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)
|
|
350
966
|
|
|
351
967
|
## License
|
|
352
968
|
|
|
353
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.
|