sqa 0.0.24 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. checksums.yaml +4 -4
  2. data/.goose/memory/development.txt +3 -0
  3. data/.semver +6 -0
  4. data/ARCHITECTURE.md +648 -0
  5. data/CHANGELOG.md +95 -0
  6. data/CLAUDE.md +674 -0
  7. data/COMMITS.md +196 -0
  8. data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
  9. data/NEXT-STEPS.md +154 -0
  10. data/README.md +839 -265
  11. data/TASKS.md +358 -0
  12. data/TEST_RESULTS.md +140 -0
  13. data/TODO.md +42 -0
  14. data/_notes.txt +25 -0
  15. data/bin/sqa-console +11 -0
  16. data/data/talk_talk.json +103284 -0
  17. data/develop_summary.md +313 -0
  18. data/docs/advanced/backtesting.md +206 -0
  19. data/docs/advanced/ensemble.md +68 -0
  20. data/docs/advanced/fpop.md +153 -0
  21. data/docs/advanced/index.md +112 -0
  22. data/docs/advanced/multi-timeframe.md +67 -0
  23. data/docs/advanced/pattern-matcher.md +75 -0
  24. data/docs/advanced/portfolio-optimizer.md +79 -0
  25. data/docs/advanced/portfolio.md +166 -0
  26. data/docs/advanced/risk-management.md +210 -0
  27. data/docs/advanced/strategy-generator.md +158 -0
  28. data/docs/advanced/streaming.md +209 -0
  29. data/docs/ai_and_ml.md +80 -0
  30. data/docs/api/dataframe.md +1114 -0
  31. data/docs/api/index.md +126 -0
  32. data/docs/assets/css/custom.css +88 -0
  33. data/docs/assets/images/sqa.jpg +0 -0
  34. data/docs/assets/js/mathjax.js +18 -0
  35. data/docs/concepts/index.md +60 -0
  36. data/docs/contributing/index.md +60 -0
  37. data/docs/data-sources/index.md +66 -0
  38. data/docs/data_frame.md +316 -97
  39. data/docs/factors_that_impact_price.md +26 -0
  40. data/docs/finviz.md +11 -0
  41. data/docs/fx_pro_bit.md +25 -0
  42. data/docs/genetic_programming.md +104 -0
  43. data/docs/getting-started/index.md +107 -0
  44. data/docs/getting-started/installation.md +229 -0
  45. data/docs/getting-started/quick-start.md +244 -0
  46. data/docs/i_gotta_an_idea.md +22 -0
  47. data/docs/index.md +161 -0
  48. data/docs/indicators/index.md +97 -0
  49. data/docs/indicators.md +110 -24
  50. data/docs/options.md +8 -0
  51. data/docs/strategies/bollinger-bands.md +146 -0
  52. data/docs/strategies/consensus.md +64 -0
  53. data/docs/strategies/custom.md +310 -0
  54. data/docs/strategies/ema.md +53 -0
  55. data/docs/strategies/index.md +92 -0
  56. data/docs/strategies/kbs.md +164 -0
  57. data/docs/strategies/macd.md +96 -0
  58. data/docs/strategies/market-profile.md +54 -0
  59. data/docs/strategies/mean-reversion.md +58 -0
  60. data/docs/strategies/rsi.md +95 -0
  61. data/docs/strategies/sma.md +55 -0
  62. data/docs/strategies/stochastic.md +63 -0
  63. data/docs/strategies/volume-breakout.md +54 -0
  64. data/docs/tags.md +7 -0
  65. data/examples/README.md +354 -0
  66. data/examples/advanced_features_example.rb +350 -0
  67. data/examples/fpop_analysis_example.rb +191 -0
  68. data/examples/genetic_programming_example.rb +148 -0
  69. data/examples/kbs_strategy_example.rb +208 -0
  70. data/examples/pattern_context_example.rb +300 -0
  71. data/examples/rails_app/Gemfile +34 -0
  72. data/examples/rails_app/README.md +416 -0
  73. data/examples/rails_app/app/assets/javascripts/application.js +107 -0
  74. data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
  75. data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
  76. data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
  77. data/examples/rails_app/app/controllers/application_controller.rb +22 -0
  78. data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
  79. data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
  80. data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
  81. data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
  82. data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
  83. data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
  84. data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
  85. data/examples/rails_app/app/views/errors/show.html.erb +17 -0
  86. data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
  87. data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
  88. data/examples/rails_app/bin/rails +6 -0
  89. data/examples/rails_app/config/application.rb +45 -0
  90. data/examples/rails_app/config/boot.rb +5 -0
  91. data/examples/rails_app/config/database.yml +18 -0
  92. data/examples/rails_app/config/environment.rb +11 -0
  93. data/examples/rails_app/config/routes.rb +26 -0
  94. data/examples/rails_app/config.ru +8 -0
  95. data/examples/realtime_stream_example.rb +274 -0
  96. data/examples/sinatra_app/Gemfile +42 -0
  97. data/examples/sinatra_app/Gemfile.lock +268 -0
  98. data/examples/sinatra_app/QUICKSTART.md +169 -0
  99. data/examples/sinatra_app/README.md +471 -0
  100. data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +90 -0
  101. data/examples/sinatra_app/TROUBLESHOOTING.md +95 -0
  102. data/examples/sinatra_app/app.rb +404 -0
  103. data/examples/sinatra_app/config.ru +5 -0
  104. data/examples/sinatra_app/public/css/style.css +723 -0
  105. data/examples/sinatra_app/public/debug_macd.html +82 -0
  106. data/examples/sinatra_app/public/js/app.js +107 -0
  107. data/examples/sinatra_app/start.sh +53 -0
  108. data/examples/sinatra_app/views/analyze.erb +306 -0
  109. data/examples/sinatra_app/views/backtest.erb +325 -0
  110. data/examples/sinatra_app/views/dashboard.erb +831 -0
  111. data/examples/sinatra_app/views/error.erb +58 -0
  112. data/examples/sinatra_app/views/index.erb +118 -0
  113. data/examples/sinatra_app/views/layout.erb +61 -0
  114. data/examples/sinatra_app/views/portfolio.erb +43 -0
  115. data/examples/strategy_generator_example.rb +346 -0
  116. data/hsa_portfolio.csv +11 -0
  117. data/justfile +0 -0
  118. data/lib/api/alpha_vantage_api.rb +462 -0
  119. data/lib/sqa/backtest.rb +329 -0
  120. data/lib/sqa/data_frame/alpha_vantage.rb +51 -63
  121. data/lib/sqa/data_frame/data.rb +92 -0
  122. data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
  123. data/lib/sqa/data_frame.rb +154 -243
  124. data/lib/sqa/ensemble.rb +359 -0
  125. data/lib/sqa/fpop.rb +199 -0
  126. data/lib/sqa/gp.rb +259 -0
  127. data/lib/sqa/indicator.rb +16 -6
  128. data/lib/sqa/init.rb +15 -8
  129. data/lib/sqa/market_regime.rb +240 -0
  130. data/lib/sqa/multi_timeframe.rb +379 -0
  131. data/lib/sqa/pattern_matcher.rb +497 -0
  132. data/lib/sqa/portfolio.rb +260 -6
  133. data/lib/sqa/portfolio_optimizer.rb +377 -0
  134. data/lib/sqa/risk_manager.rb +442 -0
  135. data/lib/sqa/seasonal_analyzer.rb +209 -0
  136. data/lib/sqa/sector_analyzer.rb +300 -0
  137. data/lib/sqa/stock.rb +131 -127
  138. data/lib/sqa/strategy/bollinger_bands.rb +42 -0
  139. data/lib/sqa/strategy/consensus.rb +5 -2
  140. data/lib/sqa/strategy/kbs_strategy.rb +470 -0
  141. data/lib/sqa/strategy/macd.rb +46 -0
  142. data/lib/sqa/strategy/mp.rb +1 -1
  143. data/lib/sqa/strategy/stochastic.rb +60 -0
  144. data/lib/sqa/strategy/volume_breakout.rb +57 -0
  145. data/lib/sqa/strategy.rb +5 -0
  146. data/lib/sqa/strategy_generator.rb +947 -0
  147. data/lib/sqa/stream.rb +361 -0
  148. data/lib/sqa/ticker.rb +9 -2
  149. data/lib/sqa/version.rb +1 -7
  150. data/lib/sqa.rb +35 -20
  151. data/main.just +81 -0
  152. data/mkdocs.yml +252 -0
  153. data/trace.log +0 -0
  154. metadata +265 -69
  155. data/bin/sqa +0 -6
  156. data/docs/alpha_vantage_technical_indicators.md +0 -62
  157. data/docs/average_true_range.md +0 -9
  158. data/docs/bollinger_bands.md +0 -15
  159. data/docs/candlestick_pattern_recognizer.md +0 -4
  160. data/docs/donchian_channel.md +0 -5
  161. data/docs/double_top_bottom_pattern.md +0 -3
  162. data/docs/exponential_moving_average.md +0 -19
  163. data/docs/fibonacci_retracement.md +0 -30
  164. data/docs/head_and_shoulders_pattern.md +0 -3
  165. data/docs/market_profile.md +0 -4
  166. data/docs/momentum.md +0 -19
  167. data/docs/moving_average_convergence_divergence.md +0 -23
  168. data/docs/peaks_and_valleys.md +0 -11
  169. data/docs/relative_strength_index.md +0 -6
  170. data/docs/simple_moving_average.md +0 -8
  171. data/docs/stochastic_oscillator.md +0 -4
  172. data/docs/ta_lib.md +0 -160
  173. data/docs/true_range.md +0 -12
  174. data/lib/patches/dry-cli.rb +0 -228
  175. data/lib/sqa/activity.rb +0 -10
  176. data/lib/sqa/cli.rb +0 -62
  177. data/lib/sqa/commands/analysis.rb +0 -309
  178. data/lib/sqa/commands/base.rb +0 -139
  179. data/lib/sqa/commands/web.rb +0 -199
  180. data/lib/sqa/commands.rb +0 -22
  181. data/lib/sqa/constants.rb +0 -23
  182. data/lib/sqa/indicator/average_true_range.rb +0 -33
  183. data/lib/sqa/indicator/bollinger_bands.rb +0 -28
  184. data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
  185. data/lib/sqa/indicator/donchian_channel.rb +0 -29
  186. data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
  187. data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
  188. data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
  189. data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
  190. data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
  191. data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
  192. data/lib/sqa/indicator/market_profile.rb +0 -32
  193. data/lib/sqa/indicator/mean_reversion.rb +0 -37
  194. data/lib/sqa/indicator/momentum.rb +0 -28
  195. data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
  196. data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
  197. data/lib/sqa/indicator/predict_next_value.rb +0 -202
  198. data/lib/sqa/indicator/relative_strength_index.rb +0 -47
  199. data/lib/sqa/indicator/simple_moving_average.rb +0 -24
  200. data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
  201. data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
  202. data/lib/sqa/indicator/true_range.rb +0 -39
  203. data/lib/sqa/trade.rb +0 -26
data/README.md CHANGED
@@ -1,423 +1,997 @@
1
- # SQA - Simple Qualitative Analysis
1
+ <div align="center">
2
+ <h1>SQA - Simple Qualitative Analysis</h1>
3
+
4
+ <p>A Ruby library for technical analysis of stock market data, trading strategies, and portfolio optimization.<br/>
5
+ Core library of the <a href="https://github.com/MadBomber/sqa">SQA</a> (Simple Qualitative Analysis) ecosystem.</p>
6
+ </div>
7
+
8
+ <table>
9
+ <tr>
10
+ <td width="30%" valign="middle" align="center">
11
+ <img src="docs/assets/images/sqa.jpg" alt="Ruby Turns Information into Knowledge" width="80%">
12
+ <br/>
13
+ <a href="https://www.youtube.com/watch?v=VqomZQMZQCQ">
14
+ <img src="https://img.shields.io/badge/Badge-We%20don't%20need%20no%20stinkin'%20badges!-red" alt="Badges?">
15
+ </a>
16
+ </td>
17
+ <td width="70%" valign="top">
18
+
19
+ **⚠️ 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.
20
+
21
+ ## Features
22
+
23
+ ### Core Capabilities
24
+ - **High-Performance DataFrames** - Polars-based data structures for time series financial data
25
+ - **150+ Technical Indicators** - Via the separate [`sqa-tai`](https://github.com/MadBomber/sqa-tai) gem (TA-Lib wrapper)
26
+ - **Trading Strategies** - Framework for building and testing trading strategies
27
+ - **Multiple Data Sources** - Alpha Vantage and Yahoo Finance adapters
28
+ - **Stock Management** - Track stocks with historical prices and company metadata
29
+ - **Statistical Analysis** - Comprehensive statistics on price data
30
+ - **Ticker Validation** - Validate stock symbols against market exchanges
31
+ - **Interactive Console** - IRB console for experimentation (`sqa-console`)
32
+ - **CLI Tools** - Command-line interface via the separate [`sqa-cli`](https://github.com/MadBomber/sqa-cli) gem
33
+
34
+ ### Advanced Features
35
+ - **Portfolio Management** - Track positions, trades, P&L with commission support
36
+ - **Backtesting Framework** - Simulate trading strategies with comprehensive performance metrics
37
+ - **Strategy Generator** - Reverse-engineer profitable trades to discover indicator patterns
38
+ - **Genetic Programming** - Evolve optimal strategy parameters through natural selection
39
+ - **Knowledge-Based Strategy (KBS)** - RETE-based forward-chaining inference for complex trading rules
40
+ - **Real-Time Streaming** - Event-driven live price processing with on-the-fly signal generation
41
+ - **FPL Analysis** - Future Period Loss/Profit analysis with risk metrics and directional classification
42
+ - **Market Regime Detection** - Identify bull/bear/sideways markets with volatility and strength metrics
43
+ - **Seasonal Analysis** - Discover calendar-dependent patterns (monthly/quarterly performance)
44
+ - **Sector Analysis** - Multi-stock pattern discovery with KBS blackboards per sector
45
+ - **Walk-Forward Validation** - Time-series cross-validation to prevent pattern overfitting
46
+ - **Pattern Context System** - Context-aware patterns that know when/where they're valid
47
+
48
+ </td>
49
+ </tr>
50
+ </table>
2
51
 
3
- [![Badges?](https://img.shields.io/badge/Badge-We%20don't%20need%20no%20stinkin'%20badges!-red)](https://www.youtube.com/watch?v=VqomZQMZQCQ)
52
+ <!-- Tocer[start]: Auto-generated, don't remove. -->
4
53
 
5
- This is a very simplistic set of tools for running technical analysis (quantitative and qualitative) on a stock portfolio. Simplistic means it is not reliable nor intended for any kind of mission-critical financial use. Think of it as a training tool. I do. Its helping me understand why I need professional help from people who know what they are doing.
54
+ ## Table of Contents
6
55
 
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.
56
+ - [Features](#features)
57
+ - [Installation](#installation)
58
+ - [Configuration](#configuration)
59
+ - [Data Directory](#data-directory)
60
+ - [Alpha Vantage API Key](#alpha-vantage-api-key)
61
+ - [Usage](#usage)
62
+ - [Working with Stocks](#working-with-stocks)
63
+ - [DataFrame Operations](#dataframe-operations)
64
+ - [Technical Indicators](#technical-indicators)
65
+ - [Trading Strategies](#trading-strategies)
66
+ - [Statistics](#statistics)
67
+ - [Interactive Console](#interactive-console)
68
+ - [Architecture](#architecture)
69
+ - [Data Sources](#data-sources)
70
+ - [Alpha Vantage](#alpha-vantage)
71
+ - [Yahoo Finance](#yahoo-finance)
72
+ - [Contributing](#contributing)
73
+ - [License](#license)
8
74
 
9
- <!-- TODO: Consider these gems ...
75
+ <!-- Tocer[finish]: Auto-generated, don't remove. -->
76
+ ## Installation
10
77
 
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
78
+ Install the gem:
13
79
 
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
80
+ ```bash
81
+ gem install sqa
82
+ ```
16
83
 
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
84
+ This will automatically install the `sqa-tai` gem (technical indicators) as a dependency.
19
85
 
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
86
+ Or add to your `Gemfile`:
22
87
 
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
88
+ ```ruby
89
+ gem 'sqa'
90
+ ```
25
91
 
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
92
+ Then run:
28
93
 
29
- -->
94
+ ```bash
95
+ bundle install
96
+ ```
30
97
 
98
+ For command-line tools, install the separate CLI gem:
31
99
 
100
+ ```bash
101
+ gem install sqa-cli
102
+ ```
32
103
 
33
- <!-- Tocer[start]: Auto-generated, don't remove. -->
104
+ ## Configuration
34
105
 
35
- ## Table of Contents
106
+ ### Data Directory
36
107
 
37
- - [This is a Work in Progress](#this-is-a-work-in-progress)
38
- - [Recent Changes](#recent-changes)
39
- - [Installation](#installation)
40
- - [Semantic Versioning](#semantic-versioning)
41
- - [Usage](#usage)
42
- - [Setup a Config File](#setup-a-config-file)
43
- - [AlphaVantage](#alphavantage)
44
- - [TradingView](#tradingview)
45
- - [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
- - [Contributing](#contributing)
59
- - [License](#license)
108
+ SQA stores historical price data and metadata in a data directory. By default, it uses `~/sqa_data/`:
60
109
 
61
- <!-- Tocer[finish]: Auto-generated, don't remove. -->
110
+ ```ruby
111
+ require 'sqa'
62
112
 
63
- ## This is a Work in Progress
113
+ # Initialize with default configuration
114
+ SQA.init
64
115
 
65
- I am experimenting with different gems to support various functionality. Sometimes they do not work out well. For example I've gone through two different gems to implement the data frame capability. Neither did what I wanted so I ended up creating my own data frame class based upon the old tried and true [Hashie](https://github.com/intridea/hashie) library.
116
+ # Or specify a custom data directory
117
+ SQA::Config.new(data_dir: '~/Documents/my_stock_data')
118
+ ```
66
119
 
67
- ## Recent Changes
120
+ Create your data directory:
68
121
 
69
- * 0.0.24 - Replaced tty-option with dry-cli
122
+ ```bash
123
+ mkdir ~/sqa_data
124
+ ```
70
125
 
71
- ## Installation
126
+ ### Alpha Vantage API Key
72
127
 
73
- Install the gem and add to the application's Gemfile by executing:
128
+ SQA uses Alpha Vantage for stock data. You'll need a free API key from [https://www.alphavantage.co/](https://www.alphavantage.co/)
74
129
 
75
- bundle add sqa
130
+ Set the environment variable:
76
131
 
77
- If bundler is not being used to manage dependencies, install the gem by executing:
132
+ ```bash
133
+ export AV_API_KEY="your_api_key_here"
134
+ # or
135
+ export ALPHAVANTAGE_API_KEY="your_api_key_here"
136
+ ```
78
137
 
79
- gem install sqa
138
+ The free tier allows:
139
+ - 5 API calls per minute
140
+ - 100 API calls per day
80
141
 
81
- ## Semantic Versioning
142
+ ## Usage
82
143
 
83
- `sqa` uses the [sem_version](https://github.com/canton7/sem_version) gem to provide a semantic version object. Sure its old; but, so am I. Doesn't mean we can't do the job.
144
+ ### Working with Stocks
84
145
 
85
146
  ```ruby
86
- # On the command line:
87
- sqa --version
147
+ require 'sqa'
88
148
 
89
- # or inside your program:
149
+ # Initialize SQA
150
+ SQA.init
90
151
 
91
- SQA.version # returns SemVersion object
92
- exit(1) unless SQA.version >= SemVersion("1.0.0")
152
+ # Create or load a stock (automatically fetches/updates data from Alpha Vantage)
153
+ aapl = SQA::Stock.new(ticker: 'AAPL', source: :alpha_vantage)
154
+ #=> aapl with 1207 data points from 2019-01-02 to 2023-10-17
93
155
 
94
- # Okay, you're right, you could put that kind of version
95
- # constraint in a Gemfile and let bundler handle the
96
- # dependencies for you.
97
- ```
156
+ # Access the DataFrame
157
+ aapl.df
158
+ #=> SQA::DataFrame (wraps Polars::DataFrame)
98
159
 
99
- ## Usage
160
+ # Get column names
161
+ aapl.df.columns
162
+ #=> ["timestamp", "open_price", "high_price", "low_price", "close_price", "adj_close_price", "volume"]
100
163
 
101
- **Do not use!** but its okay to play with.
164
+ # Access price data
165
+ aapl.df["adj_close_price"]
166
+ #=> Polars::Series with all adjusted closing prices
102
167
 
103
- `SQA` can be used from the command line or as a library in your own application.
168
+ # Get recent prices
169
+ prices = aapl.df["adj_close_price"].to_a
170
+ prices.last(5)
171
+ #=> [179.8, 180.71, 178.85, 178.72, 177.15]
104
172
 
105
- `SQA` has a command line component.
173
+ # Access company metadata
174
+ aapl.name
175
+ #=> "Apple Inc."
106
176
 
107
- ```plaintext
108
- $ sqa --help
109
- Stock Quantitative Analysis (SQA)
177
+ aapl.exchange
178
+ #=> "NASDAQ"
110
179
 
111
- Usage: sqa [analysis|web] [OPTIONS]
180
+ aapl.overview
181
+ #=> Hash with detailed company information
182
+ ```
112
183
 
113
- A collection of things
184
+ ### DataFrame Operations
114
185
 
115
- Options:
116
- -c, --config string Path to the config file
117
- --data-dir string Set the directory for the SQA data
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
186
+ SQA::DataFrame is a wrapper around the high-performance Polars DataFrame:
128
187
 
129
- Examples:
130
- sqa -c ~/.sqa.yml -p portfolio.csv -t trades.csv --data-dir ~/sqa_data
188
+ ```ruby
189
+ require 'sqa'
131
190
 
132
- Optional Command Available:
133
- analysis - Provide an Analysis of a Portfolio
134
- web - Run a web server
191
+ # Create from hash
192
+ data = {
193
+ timestamp: ['2023-01-01', '2023-01-02', '2023-01-03'],
194
+ price: [100.0, 101.5, 99.8]
195
+ }
196
+ df = SQA::DataFrame.new(data)
135
197
 
136
- WARNING: This is a toy, a play thing, not intended for serious use.
137
- ```
198
+ # Load from CSV
199
+ df = SQA::DataFrame.load(source: 'aapl.csv')
138
200
 
139
- More about the two included programs `analysis` and `web` later.
201
+ # Basic operations
202
+ df.size # Number of rows
203
+ df.ncols # Number of columns
204
+ df.to_h # Convert to hash
205
+ df.to_csv('output.csv') # Save to CSV
140
206
 
141
- ### Setup a Config File
207
+ # Access underlying Polars DataFrame
208
+ df.data # Polars::DataFrame
209
+ ```
142
210
 
143
- You will need to create a directory to store the `sqa` data and a configuration file. You can start by doing this:
211
+ ### Technical Indicators
212
+
213
+ All technical indicators are provided by the separate [`sqa-tai`](https://github.com/MadBomber/sqa-tai) gem, which wraps the industry-standard TA-Lib library. The `sqa-tai` gem must be installed separately but is automatically included as a dependency of `sqa`.
144
214
 
145
215
  ```ruby
146
- gem install sqa
147
- mkdir ~/Documents/sqa_data
148
- sqa --data-dir ~/Documents/sqa_data --dump-config ~/.sqa.yml
149
- ```
216
+ require 'sqa'
150
217
 
151
- By default `SQA` looks for a configuration file named `.sqa.yml` in the current directory. If it does not find one there it looks in the home directory. You can use the `--config` CLI option to specify a path to your custom config file name.
218
+ prices = [100, 102, 105, 103, 107, 110, 108, 112, 115, 113]
152
219
 
153
- You can have multiple configurations and multiple data directories.
220
+ # Simple Moving Average
221
+ sma = SQAI.sma(prices, period: 5)
222
+ #=> [104.0, 105.4, 106.6, 108.0, 110.4, 111.6]
154
223
 
155
- You can also have one data directory and multiple portfolio and trades files within that single directory.
224
+ # Relative Strength Index
225
+ rsi = SQAI.rsi(prices, period: 14)
226
+ #=> [70.5, 68.2, ...]
156
227
 
157
- ### AlphaVantage
228
+ # Exponential Moving Average
229
+ ema = SQAI.ema(prices, period: 5)
230
+ #=> [...]
158
231
 
159
- `SQA` makes use of the `AlphaVantage` API to get some stock related information. You will need an API key in order to use this functionality. They have a free rate limited API key which allows 5 accesses per second; total of 100 accesses in a day. If you are doing more than that you are not playing an ought to purchase one of there serious API key plans.
232
+ # Bollinger Bands (returns upper, middle, lower)
233
+ upper, middle, lower = SQAI.bbands(prices, period: 5)
160
234
 
161
- [https://www.alphavantage.co/](https://www.alphavantage.co/)
235
+ # MACD
236
+ macd, signal, histogram = SQAI.macd(prices, fast_period: 12, slow_period: 26, signal_period: 9)
162
237
 
163
- Put your API key in the system environment variable `AV_API_KEY`
238
+ # Many more indicators available!
239
+ # See: https://github.com/MadBomber/sqa-tai
240
+ ```
164
241
 
165
- <!-- TODO: why is it not part of the configuration? ought to be. -->
242
+ **Available Indicators:** SMA, EMA, WMA, RSI, MACD, Bollinger Bands, Stochastic, ADX, ATR, CCI, Williams %R, ROC, Momentum, and 140+ more via TA-Lib.
166
243
 
244
+ ### Trading Strategies
167
245
 
168
- ### TradingView
246
+ Build and test trading strategies:
169
247
 
170
- **Not currently implemented** but seriously considering using [TradingView](http://tradingview.com) capabilities. If I do choose to use these capabilities you will need your own API key or account or something.
248
+ ```ruby
249
+ require 'sqa'
250
+ require 'ostruct'
171
251
 
172
- Put your API key in the system environment variable `TV_API_KEY`
252
+ # Load strategies
253
+ strategies = SQA::Strategy.new
254
+ strategies.auto_load # Loads all built-in strategies
255
+
256
+ # Add specific strategies
257
+ strategies.add SQA::Strategy::RSI
258
+ strategies.add SQA::Strategy::SMA
259
+ strategies.add SQA::Strategy::EMA
260
+
261
+ # Prepare data for strategy execution
262
+ prices = aapl.df["adj_close_price"].to_a
263
+ rsi_value = SQAI.rsi(prices, period: 14).last
264
+
265
+ vector = OpenStruct.new
266
+ vector.rsi = rsi_value
267
+ vector.prices = prices
268
+
269
+ # Execute strategies
270
+ results = strategies.execute(vector)
271
+ #=> [:hold, :buy, :hold] # One result per strategy
272
+
273
+ # Built-in strategies:
274
+ # - SQA::Strategy::RSI - Based on Relative Strength Index
275
+ # - SQA::Strategy::SMA - Simple Moving Average crossover
276
+ # - SQA::Strategy::EMA - Exponential Moving Average crossover
277
+ # - SQA::Strategy::MACD - MACD crossover strategy
278
+ # - SQA::Strategy::BollingerBands - Bollinger Bands bounce strategy
279
+ # - SQA::Strategy::Stochastic - Stochastic oscillator strategy
280
+ # - SQA::Strategy::VolumeBreakout - Volume-based breakout strategy
281
+ # - SQA::Strategy::MR - Mean Reversion
282
+ # - SQA::Strategy::MP - Market Profile
283
+ # - SQA::Strategy::KBS - Knowledge-based RETE strategy (advanced)
284
+ # - SQA::Strategy::Random - Random signal generator (for testing)
285
+ # - SQA::Strategy::Consensus - Combines multiple strategies
286
+ ```
173
287
 
174
- <!-- TODO: why is it not part of the configuration? ought to be. -->
288
+ ### Statistics
175
289
 
176
- I kinda like their Terms of Use. I've started crafting an [SQA Terms of Use](docs/terms_of_use.md) document based on theirs. I specifically like sections 7 and 8.
290
+ Comprehensive statistical analysis on price data (via `lite-statistics` gem):
177
291
 
178
- ### Yahoo Finance
292
+ ```ruby
293
+ require 'sqa'
179
294
 
180
- The finance.yahoo.com website no longer supports an API. To get information from this website you must scrape it. That is how historical / receint stock prices are being obtained in the DataFrame::YahooFinance class. I do not like doing this and do not necessarily recommend using YahooFinance as a source for stock price data.
295
+ prices = [179.8, 180.71, 178.85, 178.72, 177.15]
296
+
297
+ stats = prices.summary
298
+ #=> {
299
+ # max: 180.71,
300
+ # min: 177.15,
301
+ # mean: 179.046,
302
+ # median: 178.85,
303
+ # mode: nil,
304
+ # range: 3.56,
305
+ # sample_standard_deviation: 1.19,
306
+ # sample_variance: 1.42,
307
+ # ...
308
+ # }
309
+ ```
181
310
 
182
- In the SQA::Stock class the default source is AlphaVantage which has its own limitations.
311
+ ### Portfolio Management
183
312
 
184
- This is not to say that finance.yahoo.com has no use. In fact is is the perfect place to go to manually download CSV files of historical stock price data. When you do that through your browser, the CSV file will be placed in your default downloads directory.
313
+ Track positions, trades, and P&L:
185
314
 
186
- To use this downed file within the SQA environment it must be moved into your `SQA.`data_dir` with a filename that is all lowercase. The filename must be the stock's symbol with a `.csv` extension. For example if you downloaded the entire historical stock price data for Apple Computer (AAPL) the filename in the SQA.data_dir should be "aapl.csv"
315
+ ```ruby
316
+ require 'sqa'
187
317
 
188
- You can manually go to the Yahoo Finance website at [https://finance.yahoo.com/quote/AAPL/history?p=AAPL](https://finance.yahoo.com/quote/AAPL/history?p=AAPL)
318
+ # Create portfolio with initial cash and commission
319
+ portfolio = SQA::Portfolio.new(initial_cash: 10_000.0, commission: 1.0)
189
320
 
321
+ # Buy stock
322
+ portfolio.buy('AAPL', shares: 10, price: 150.0, date: Date.today)
190
323
 
191
- ## Playing in IRB
324
+ # Sell stock
325
+ portfolio.sell('AAPL', shares: 5, price: 160.0, date: Date.today)
192
326
 
193
- You can play around in IRB with the SQA framework.
327
+ # Check portfolio value
328
+ current_prices = { 'AAPL' => 165.0 }
329
+ portfolio.value(current_prices)
330
+ #=> Total portfolio value
194
331
 
332
+ # Calculate profit/loss
333
+ portfolio.profit_loss(current_prices)
334
+ #=> P&L amount
195
335
 
196
- ### With Stocks and Indicators
336
+ # View summary
337
+ portfolio.summary
338
+ #=> { initial_cash: 10000.0, cash: 8248.0, positions: 1, trades: 2, ... }
197
339
 
198
- You will need some CSV files. If you ask for a stock to which you have not existing historical price data in a CSV file, `SQA` can use either Alpha Vantage or Yahoo Finance to get some data. I like Alpha Vantage better because it has a well defined and documented API. Yahoo Finance on the other hand does not. You can manually download historical stock price data from Yahoo Finance into you `sqa data directory`
340
+ # Save trades to CSV
341
+ portfolio.save_to_csv('my_trades.csv')
199
342
 
200
- Historical price data is kept in the `SQA.data_dir` in a CSV file whose name is all lowercase. If you download the CSV file for the stock symbol "AAPL" it should be saved in you `SQA.data_dir` as `aapl.csv`
343
+ # Load from CSV
344
+ portfolio.load_from_csv('my_trades.csv')
345
+ ```
201
346
 
202
- #### Get Historical Prices
347
+ ### Backtesting
203
348
 
204
- Go to [https::/finance.yahoo.com](https::/finance.yahoo.com) and down some historical price data for your favorite stocks. Put those CSV files into the `SQA.data_dir`.
349
+ Simulate trading strategies against historical data:
205
350
 
206
- You may need to create a `portfolio.csv` file or you may not.
351
+ ```ruby
352
+ require 'sqa'
207
353
 
208
- <!-- TODO: Add a section on how to create a portfolio fCSV file -->
354
+ SQA.init
355
+ stock = SQA::Stock.new(ticker: 'AAPL')
356
+
357
+ # Create backtest
358
+ backtest = SQA::Backtest.new(
359
+ stock: stock,
360
+ strategy: SQA::Strategy::RSI,
361
+ initial_capital: 10_000.0,
362
+ commission: 1.0
363
+ )
364
+
365
+ # Run backtest
366
+ results = backtest.run
367
+
368
+ # View comprehensive metrics
369
+ puts "Total Return: #{results.total_return.round(2)}%"
370
+ puts "Annualized Return: #{results.annualized_return.round(2)}%"
371
+ puts "Sharpe Ratio: #{results.sharpe_ratio.round(2)}"
372
+ puts "Max Drawdown: #{results.max_drawdown.round(2)}%"
373
+ puts "Total Trades: #{results.total_trades}"
374
+ puts "Win Rate: #{results.win_rate.round(2)}%"
375
+ puts "Profit Factor: #{results.profit_factor.round(2)}"
376
+
377
+ # Access equity curve for charting
378
+ results.equity_curve #=> Array of portfolio values over time
379
+ ```
209
380
 
210
- The CSV files will be named by the stock's ticker symbol as lower case. For example: `aapl.csv`
381
+ ### Strategy Generator
211
382
 
212
- ### Playing in the IRB - Setup
383
+ Reverse-engineer profitable trades to discover winning patterns:
213
384
 
214
385
  ```ruby
215
386
  require 'sqa'
216
- require 'sqa/cli'
217
-
218
- # TODO: Is this still true after the dry-cli integration?
219
- #
220
- # You can pass a set of CLI options in a String
221
- SQA.init "-c ~/.sqa.yml"
222
387
 
223
- # If you have an API key for AlphaVantage you can create a new
224
- # CSV file, or update an existing one. Your key MUST be
225
- # in the system environment variable AV_API_KEY
226
- aapl = SQA::Stock.new(ticker: 'aapl', source: :alpha_vantage)
227
- #=> aapl with 1207 data points from 2019-01-02 to 2023-10-17
388
+ SQA.init
389
+ stock = SQA::Stock.new(ticker: 'AAPL')
390
+
391
+ # Create strategy generator
392
+ generator = SQA::StrategyGenerator.new(
393
+ stock: stock,
394
+ min_gain_percent: 10.0, # Find trades with ≥10% gain
395
+ holding_period: (5..20) # Within 5-20 days
396
+ )
397
+
398
+ # Discover patterns
399
+ patterns = generator.discover_patterns(min_pattern_frequency: 3)
400
+
401
+ # Print discovered patterns
402
+ generator.print_patterns(max_patterns: 10)
403
+ #=> Pattern #1:
404
+ #=> Frequency: 15 occurrences
405
+ #=> Average Gain: 12.5%
406
+ #=> Average Holding: 8.3 days
407
+ #=> Conditions:
408
+ #=> - rsi: oversold
409
+ #=> - macd_crossover: bullish
410
+ #=> - volume: high
411
+
412
+ # Generate strategy from top pattern
413
+ strategy = generator.generate_strategy(pattern_index: 0, strategy_type: :class)
414
+
415
+ # Backtest the discovered strategy
416
+ backtest = SQA::Backtest.new(stock: stock, strategy: strategy)
417
+ results = backtest.run
418
+
419
+ # Export patterns to CSV
420
+ generator.export_patterns('/tmp/patterns.csv')
228
421
  ```
229
422
 
230
- `aapl.df` is the data frame. It is implemented as a Hashie::Mash obect -- a Hash or Arrays.
423
+ ### Genetic Programming
424
+
425
+ Evolve optimal strategy parameters through natural selection:
231
426
 
232
427
  ```ruby
233
- aapl.df.keys
234
- #=> [:timestamp, :open_price, :high_price, :low_price, :close_price, :adj_close_price, :volume]
428
+ require 'sqa'
235
429
 
236
- aapl.df.adj_close_price.last(5)
237
- #=> [179.8, 180.71, 178.85, 178.72, 177.15]
430
+ SQA.init
431
+ stock = SQA::Stock.new(ticker: 'AAPL')
432
+
433
+ # Create genetic program
434
+ gp = SQA::GeneticProgram.new(
435
+ stock: stock,
436
+ population_size: 50,
437
+ generations: 100,
438
+ mutation_rate: 0.15
439
+ )
440
+
441
+ # Define parameter space to explore
442
+ gp.define_genes(
443
+ rsi_period: (7..30).to_a,
444
+ buy_threshold: (20..40).to_a,
445
+ sell_threshold: (60..80).to_a
446
+ )
447
+
448
+ # Define fitness function (backtest performance)
449
+ gp.fitness do |genes|
450
+ # Create strategy with these genes
451
+ strategy = create_rsi_strategy(genes)
452
+
453
+ # Backtest and return total return as fitness
454
+ backtest = SQA::Backtest.new(stock: stock, strategy: strategy)
455
+ backtest.run.total_return
456
+ end
457
+
458
+ # Evolve!
459
+ best = gp.evolve
460
+
461
+ puts "Best Parameters:"
462
+ puts " RSI Period: #{best.genes[:rsi_period]}"
463
+ puts " Buy Threshold: #{best.genes[:buy_threshold]}"
464
+ puts " Sell Threshold: #{best.genes[:sell_threshold]}"
465
+ puts " Fitness: #{best.fitness.round(2)}%"
466
+
467
+ # View evolution history
468
+ gp.history.each do |gen|
469
+ puts "Gen #{gen[:generation]}: Best=#{gen[:best_fitness].round(2)}%"
470
+ end
238
471
  ```
239
472
 
240
- `aapl.data` is basic static data, company name, industry etc. It is also implemented as a Hassie::Mash object but is primary treated as a plain hash object.
473
+ ### Knowledge-Based Strategy (KBS)
474
+
475
+ Build sophisticated rule-based systems with RETE forward chaining:
241
476
 
242
477
  ```ruby
243
- aapl.data.keys
244
- #=> [:ticker, :source, :indicators, :overview]
478
+ require 'sqa'
245
479
 
246
- aapl.data.source
247
- => "alpha_vantage"
480
+ # Create KBS strategy
481
+ strategy = SQA::Strategy::KBS.new(load_defaults: false)
482
+
483
+ # Define custom trading rules
484
+ strategy.add_rule :golden_opportunity do
485
+ desc "Perfect storm: Multiple bullish indicators align"
486
+
487
+ # Multiple conditions
488
+ on :rsi, { level: :oversold }
489
+ on :macd, { crossover: :bullish }
490
+ on :stochastic, { zone: :oversold, crossover: :bullish }
491
+ on :trend, { short_term: :up, strength: :strong }
492
+ on :volume, { level: :high }
493
+
494
+ # Negation: Don't buy if overbought elsewhere
495
+ without :rsi, { level: :overbought }
496
+
497
+ # Action
498
+ then do
499
+ assert(:signal, { action: :buy, confidence: :high })
500
+ end
501
+ end
502
+
503
+ # Execute strategy
504
+ signal = strategy.execute(vector)
505
+ #=> :buy or :sell or :hold
506
+
507
+ # Use with backtesting
508
+ backtest = SQA::Backtest.new(
509
+ stock: stock,
510
+ strategy: SQA::Strategy::KBS,
511
+ initial_capital: 10_000.0
512
+ )
513
+ results = backtest.run
514
+ ```
248
515
 
249
- aapl.data.overciew.keys
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]
516
+ ### Real-Time Streaming
251
517
 
518
+ Process live stock prices and generate on-the-fly trading signals:
519
+
520
+ ```ruby
521
+ require 'sqa'
522
+
523
+ # Create stream processor
524
+ stream = SQA::Stream.new(
525
+ ticker: 'AAPL',
526
+ window_size: 100,
527
+ strategies: [
528
+ SQA::Strategy::RSI,
529
+ SQA::Strategy::MACD,
530
+ SQA::Strategy::BollingerBands
531
+ ]
532
+ )
533
+
534
+ # Register signal callback
535
+ stream.on_signal do |signal, data|
536
+ puts "🔔 SIGNAL: #{signal.upcase}"
537
+ puts " Price: $#{data[:price]}"
538
+ puts " Consensus: #{data[:strategies_vote]}"
539
+
540
+ # Execute trade, send alert, etc.
541
+ execute_trade(signal, data) if signal != :hold
542
+ end
543
+
544
+ # Register update callback (optional)
545
+ stream.on_update do |data|
546
+ puts "📊 Price update: $#{data[:price]}"
547
+ end
548
+
549
+ # Feed live data (from WebSocket, API, etc.)
550
+ stream.update(
551
+ price: 150.25,
552
+ volume: 1_000_000,
553
+ timestamp: Time.now
554
+ )
555
+
556
+ # Access real-time indicators
557
+ rsi = stream.indicator(:rsi, period: 14)
558
+ sma = stream.indicator(:sma, period: 20)
559
+
560
+ # View stream statistics
561
+ stream.stats
562
+ #=> { ticker: 'AAPL', updates: 125, current_price: 150.25, ... }
252
563
  ```
253
564
 
254
- ### Playing in the IRB - Statistics
565
+ ### FPL Analysis (Future Period Loss/Profit)
255
566
 
256
- Basic statistics are available on all of the SQA::DataFrame arrays.
567
+ Analyze potential future price movements with risk metrics:
257
568
 
258
569
  ```ruby
259
- require 'amazing_print' # to get the ap command
260
-
261
- # Look at some summary stats on the last 5 days of
262
- # adjusted closing pricess of AAPL
263
- ap aapl.df.adj_close_price.last(5).summary
264
- {
265
- :frequencies => {
266
- 179.8 => 1,
267
- 180.71 => 1,
268
- 178.85 => 1,
269
- 178.72 => 1,
270
- 177.15 => 1
271
- },
272
- :max => 180.71,
273
- :mean => 179.046,
274
- :median => 178.85,
275
- :midrange => 178.93,
276
- :min => 177.15,
277
- :mode => nil,
278
- :proportions => {
279
- 179.8 => 0.2,
280
- 180.71 => 0.2,
281
- 178.85 => 0.2,
282
- 178.72 => 0.2,
283
- 177.15 => 0.2
284
- },
285
- :quartile1 => 178.85,
286
- :quartile2 => 179.8,
287
- :quartile3 => 180.71,
288
- :range => 3.5600000000000023,
289
- :size => 5,
290
- :sum => 895.23,
291
- :sample_coefficient_of_variation => 0.006644656242680533,
292
- :sample_kurtosis => 2.089087404921432,
293
- :sample_size => 5,
294
- :sample_skewness => -0.2163861377512453,
295
- :sample_standard_deviation => 1.1896991216269788,
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
570
+ require 'sqa'
571
+
572
+ stock = SQA::Stock.new(ticker: 'AAPL')
573
+
574
+ # Calculate FPL for 14-day future period
575
+ fpl_data = stock.df.fpl(fpop: 14)
576
+ # Returns: [[min_delta, max_delta], ...] for each price point
577
+
578
+ # Full analysis with risk metrics
579
+ analysis = stock.df.fpl_analysis(fpop: 14)
580
+
581
+ analysis.first
582
+ #=> {
583
+ # min_delta: -5.2, # Worst loss percentage
584
+ # max_delta: 8.7, # Best gain percentage
585
+ # risk: 13.9, # Volatility (max - min)
586
+ # direction: :UNCERTAIN,# :UP, :DOWN, :UNCERTAIN, :FLAT
587
+ # magnitude: 1.75, # Average expected movement
588
+ # interpretation: "UNCERTAIN: 1.75% (±6.95% risk)"
589
+ #}
590
+
591
+ # Filter high-quality opportunities
592
+ good_indices = SQA::FPOP.filter_by_quality(
593
+ analysis,
594
+ min_magnitude: 5.0, # Minimum average movement
595
+ max_risk: 10.0, # Maximum acceptable volatility
596
+ directions: [:UP] # Only bullish patterns
597
+ )
598
+
599
+ # Use with Strategy Generator for risk-aware pattern discovery
600
+ generator = SQA::StrategyGenerator.new(
601
+ stock: stock,
602
+ min_gain_percent: 10.0,
603
+ fpop: 10,
604
+ max_fpl_risk: 20.0, # NEW: Filter high-volatility points
605
+ required_fpl_directions: [:UP] # NEW: Only bullish patterns
606
+ )
307
607
  ```
308
608
 
309
- ### Playing in the IRB - Indicators
609
+ **Key Benefits:**
610
+ - Captures both upside AND downside potential
611
+ - Measures volatility/risk during future period
612
+ - Classifies directional bias (UP/DOWN/UNCERTAIN/FLAT)
613
+ - Enables risk-adjusted opportunity filtering
310
614
 
311
- The SQA::Indicator class methods use Arrays not the DataFrame
312
- Here is an example:
615
+ ### Market Regime Detection
313
616
 
617
+ Identify and adapt to changing market conditions:
314
618
 
315
619
  ```ruby
316
- prices = aapl.df.adj_close_price
317
- period = 14 # size of the window in prices to analyze
620
+ # Detect current market regime
621
+ regime = SQA::MarketRegime.detect(stock)
622
+ #=> {
623
+ # type: :bull, # :bull, :bear, :sideways
624
+ # volatility: :low, # :low, :medium, :high
625
+ # strength: :strong, # :weak, :moderate, :strong
626
+ # lookback_days: 60,
627
+ # detected_at: 2024-11-08
628
+ #}
629
+
630
+ # Analyze regime history
631
+ regimes = SQA::MarketRegime.detect_history(stock)
632
+ #=> [
633
+ # { type: :bull, start_index: 0, end_index: 120, duration: 120 },
634
+ # { type: :bear, start_index: 121, end_index: 200, duration: 79 },
635
+ # ...
636
+ #]
637
+
638
+ # Split data by regime
639
+ regime_data = SQA::MarketRegime.split_by_regime(stock)
640
+ #=> {
641
+ # bull: [{ prices: [...], start_index: 0, end_index: 120 }, ...],
642
+ # bear: [...],
643
+ # sideways: [...]
644
+ #}
645
+
646
+ # Use regime-specific strategies
647
+ current_regime = SQA::MarketRegime.detect(stock)
648
+ strategy = case current_regime[:type]
649
+ when :bull
650
+ SQA::Strategy::MomentumBreakout
651
+ when :bear
652
+ SQA::Strategy::MeanReversion
653
+ when :sideways
654
+ SQA::Strategy::BollingerBands
655
+ end
656
+ ```
657
+
658
+ **Key Benefits:**
659
+ - Different strategies for different market conditions
660
+ - Avoid using bull market strategies in bear markets
661
+ - Detect regime changes before they impact performance
662
+
663
+ ### Seasonal Analysis
318
664
 
319
- rsi = SQA::Indicator.rsi(prices, period)
320
- #=> {:rsi=>63.46652828230407, :trend=>:normal}
665
+ Discover calendar-dependent patterns:
666
+
667
+ ```ruby
668
+ # Analyze seasonal performance
669
+ seasonal = SQA::SeasonalAnalyzer.analyze(stock)
670
+ #=> {
671
+ # best_months: [10, 11, 12], # October, November, December
672
+ # worst_months: [5, 6, 7], # May, June, July
673
+ # best_quarters: [4, 1], # Q4 and Q1
674
+ # worst_quarters: [2, 3],
675
+ # has_seasonal_pattern: true,
676
+ # monthly_returns: { ... },
677
+ # quarterly_returns: { ... }
678
+ #}
679
+
680
+ # Filter data for Q4 only (holiday shopping season)
681
+ q4_data = SQA::SeasonalAnalyzer.filter_by_quarters(stock, [4])
682
+ #=> { indices: [...], dates: [...], prices: [...] }
683
+
684
+ # Filter for specific months
685
+ dec_data = SQA::SeasonalAnalyzer.filter_by_months(stock, [12])
686
+
687
+ # Get context for specific date
688
+ context = SQA::SeasonalAnalyzer.context_for_date(Date.new(2024, 12, 15))
689
+ #=> {
690
+ # month: 12,
691
+ # quarter: 4,
692
+ # month_name: "December",
693
+ # quarter_name: "Q4",
694
+ # is_year_end: true,
695
+ # is_holiday_season: true,
696
+ # is_earnings_season: false
697
+ #}
698
+
699
+ # Discover patterns only for best months
700
+ generator = SQA::StrategyGenerator.new(stock: stock)
701
+ patterns = generator.discover_context_aware_patterns(
702
+ analyze_seasonal: true
703
+ )
704
+
705
+ patterns.each do |pattern|
706
+ puts pattern.context.valid_months # [10, 11, 12] for Q4 patterns
707
+ end
321
708
  ```
322
709
 
323
- ### Playing in the IRB - Strategies
710
+ **Real-World Examples:**
711
+ - Retail stocks: Q4 holiday shopping boost
712
+ - Tax prep stocks: Q1/Q4 seasonal surge
713
+ - Energy stocks: Summer driving season
714
+ - Agriculture stocks: Planting/harvest cycles
715
+
716
+ ### Sector Analysis with KBS
324
717
 
325
- The strategies work off of an Object that contains the information required to make its recommendation. Building on the previous Ruby snippet ...
718
+ Analyze patterns across related stocks using KBS blackboards:
326
719
 
327
720
  ```ruby
328
- require 'ostruct'
329
- vector = OpenStruct.new
330
- vector.rsi = rsi
331
-
332
- # Load some trading strategies
333
- ss = SWA::Strategy.new
334
- ss.auto_load # loads everything in the lib/sqa/strategy folder
335
-
336
- # Select some strategies to execute
337
- ss.add SWA::Strategy::Random # 3-sided coin flip
338
- ss.add SQA::Strategy::RSI
339
-
340
- # This is an Array with each "trade" method
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)
721
+ # Create sector analyzer (separate blackboard per sector)
722
+ analyzer = SQA::SectorAnalyzer.new
723
+
724
+ # Add stocks to technology sector
725
+ analyzer.add_stock('AAPL', sector: :technology)
726
+ analyzer.add_stock('MSFT', sector: :technology)
727
+ analyzer.add_stock('GOOGL', sector: :technology)
728
+
729
+ # Detect sector-wide regime
730
+ tech_stocks = [
731
+ SQA::Stock.new(ticker: 'AAPL'),
732
+ SQA::Stock.new(ticker: 'MSFT'),
733
+ SQA::Stock.new(ticker: 'GOOGL')
346
734
  ]
347
735
 
348
- # Execute those strategies
349
- results = ss.execute(vector)
350
- #=> [:hold, :hold]
736
+ sector_regime = analyzer.detect_sector_regime(:technology, tech_stocks)
737
+ #=> {
738
+ # sector: :technology,
739
+ # consensus_regime: :bull, # Majority vote
740
+ # sector_strength: 85.0, # % of stocks bullish
741
+ # stock_regimes: [...]
742
+ #}
743
+
744
+ # Discover patterns across entire sector
745
+ sector_patterns = analyzer.discover_sector_patterns(
746
+ :technology,
747
+ tech_stocks,
748
+ min_gain_percent: 10.0,
749
+ fpop: 10
750
+ )
751
+
752
+ # Patterns that work for MULTIPLE stocks in sector
753
+ sector_patterns.each do |sp|
754
+ puts "Pattern found in: #{sp[:stocks].join(', ')}"
755
+ puts "Avg gain: #{sp[:avg_gain].round(2)}%"
756
+ puts "Conditions: #{sp[:conditions]}"
757
+ end
758
+
759
+ # Query sector blackboard
760
+ facts = analyzer.query_sector(:technology, :sector_pattern)
761
+
762
+ # Print sector summary
763
+ analyzer.print_sector_summary(:technology)
764
+ ```
765
+
766
+ **Available Sectors:**
767
+ `:technology`, `:finance`, `:healthcare`, `:energy`, `:consumer`, `:industrial`, `:materials`, `:utilities`, `:real_estate`, `:communications`
768
+
769
+ **Key Benefits:**
770
+ - Leverage "stocks move together" in same sector
771
+ - Find patterns valid across multiple related stocks
772
+ - Persistent KBS blackboard tracks sector knowledge
773
+ - Cross-stock pattern validation
774
+
775
+ ### Walk-Forward Validation
776
+
777
+ Prevent overfitting with time-series cross-validation:
778
+
779
+ ```ruby
780
+ generator = SQA::StrategyGenerator.new(
781
+ stock: stock,
782
+ min_gain_percent: 10.0,
783
+ fpop: 10
784
+ )
785
+
786
+ # Run walk-forward validation
787
+ results = generator.walk_forward_validate(
788
+ train_size: 250, # 1 year training window
789
+ test_size: 60, # 3 months testing window
790
+ step_size: 30 # Step forward 1 month each iteration
791
+ )
792
+
793
+ # Only patterns that work out-of-sample
794
+ validated_patterns = results[:validated_patterns]
795
+
796
+ # Detailed results for each iteration
797
+ results[:validation_results].each do |r|
798
+ puts "Train: #{r[:train_period]}"
799
+ puts "Test: #{r[:test_period]}"
800
+ puts "Return: #{r[:test_return]}%"
801
+ puts "Sharpe: #{r[:test_sharpe]}"
802
+ end
803
+
804
+ # Statistics
805
+ all_returns = results[:validation_results].map { |r| r[:test_return] }
806
+ avg_return = all_returns.sum / all_returns.size
807
+ puts "Average out-of-sample return: #{avg_return.round(2)}%"
808
+ ```
809
+
810
+ **How It Works:**
811
+ 1. Split data into train/test windows
812
+ 2. Discover patterns on training data
813
+ 3. Test patterns on future (unseen) data
814
+ 4. Roll forward and repeat
815
+ 5. Only keep patterns that work out-of-sample
816
+
817
+ **Prevents:**
818
+ - Curve-fitting to historical noise
819
+ - Patterns that only worked in the past
820
+ - Overconfidence in backtest results
821
+
822
+ ### Pattern Context System
823
+
824
+ Context-aware patterns that know when/where they're valid:
825
+
826
+ ```ruby
827
+ # Discover patterns with full context
828
+ generator = SQA::StrategyGenerator.new(stock: stock, min_gain_percent: 10.0)
829
+
830
+ patterns = generator.discover_context_aware_patterns(
831
+ analyze_regime: true,
832
+ analyze_seasonal: true,
833
+ sector: :technology
834
+ )
835
+
836
+ # Each pattern has rich context metadata
837
+ pattern = patterns.first
838
+ pattern.context.market_regime #=> :bull
839
+ pattern.context.volatility_regime #=> :low
840
+ pattern.context.valid_months #=> [10, 11, 12]
841
+ pattern.context.valid_quarters #=> [4, 1]
842
+ pattern.context.sector #=> :technology
843
+ pattern.context.discovered_period #=> "2020-01-01 to 2022-12-31"
844
+
845
+ # Runtime validation - check if pattern applies NOW
846
+ valid = pattern.context.valid_for?(
847
+ date: Date.today,
848
+ regime: :bull,
849
+ sector: :technology
850
+ )
851
+
852
+ if valid
853
+ # Execute strategy - context matches current conditions
854
+ signal = strategy.trade(vector)
855
+ else
856
+ # Skip - pattern not valid in current context
857
+ puts "Pattern not valid for current conditions"
858
+ end
859
+
860
+ # Pattern summary includes context
861
+ puts pattern.to_s
862
+ #=> "Pattern(conditions=3, freq=12, gain=11.5%, success=75.0%) [bull months:10,11,12 technology]"
351
863
  ```
352
864
 
353
- `results` is an Array with an entry for each strategy executed. The entries are either :buy, :sell or :hold.
865
+ **Context Metadata:**
866
+ - **Market regime**: Bull/bear/sideways classification
867
+ - **Seasonality**: Valid months/quarters
868
+ - **Sector**: Industry classification
869
+ - **Volatility regime**: Low/medium/high
870
+ - **Discovery period**: When pattern was found
871
+ - **Stability score**: Consistency over time
354
872
 
355
- Currently the strategies are executed sequentially so the results can easily be mapped back to which strategy produced which result. In the future that will change so that the strategies are executed concurrently. When that change is introduced the entries in the `results` object will change -- most likely to an Array of Hashes.
873
+ **Key Benefits:**
874
+ - Patterns aren't universal - they're context-specific
875
+ - Avoid using patterns in wrong market conditions
876
+ - Seasonal awareness (Q4 retail patterns, etc.)
877
+ - Sector-specific strategies
878
+ - Runtime validation before execution
356
879
 
357
- Any specific strategy may not work on every stock. Using the historical data, it is possible to see which strategy works better for a specific stock. **Of course the statistical motto is that historical performance is not a fail-proof indicator for future performance.**
880
+ ## Interactive Console
358
881
 
359
- The strategies that come with the `SQA::Strategy` class are examples only. Its expected that you will come up with your own. If you do, consider sharing them.
882
+ Launch an interactive Ruby console with SQA loaded:
360
883
 
361
- ## Included Program Examples
884
+ ```bash
885
+ sqa-console
886
+ ```
362
887
 
363
- <!-- TODO: What is the name of these things? From the help text they are called commands. They are treated in the code base as a class under the SQA module. Its easy to change the CLI help text to call these programs rather than commands. -->
888
+ This opens IRB with the SQA library pre-loaded, allowing you to experiment interactively:
364
889
 
365
- There are at least two included programs that make use of the `SQA` library.
890
+ ```ruby
891
+ # Already loaded: require 'sqa'
366
892
 
367
- <!-- TODO: These kinds of things need to be in their own repository. -->
893
+ SQA.init
894
+ stock = SQA::Stock.new(ticker: 'MSFT')
895
+ prices = stock.df["close_price"].to_a
896
+ sma = SQAI.sma(prices, period: 20)
897
+ ```
368
898
 
369
- <!-- TODO: Need to complete the API for invoking one of these "programs" from the sqa cli. -->
899
+ ## Examples
370
900
 
371
- ### Analysis
901
+ The `examples/` directory contains comprehensive demonstrations of advanced features:
372
902
 
373
- Does an analysis of a portfollio of stocks.
903
+ - **`genetic_programming_example.rb`** - Evolve RSI parameters through natural selection
904
+ - **`kbs_strategy_example.rb`** - Build rule-based trading systems with RETE
905
+ - **`realtime_stream_example.rb`** - Process live price streams with callbacks
906
+ - **`strategy_generator_example.rb`** - Mine profitable patterns from history
374
907
 
375
- ### Web
908
+ Run examples:
909
+ ```bash
910
+ ruby examples/genetic_programming_example.rb
911
+ ruby examples/strategy_generator_example.rb
912
+ ```
376
913
 
377
- Provides a browser-based interface to some stuff.
914
+ See `examples/README.md` for detailed documentation and integration patterns.
378
915
 
379
- ## Predicted FAQ
916
+ ## Architecture
380
917
 
381
- What is the purpose of the SQA project? The madbomber/sqa project is a set of tools for running technical analysis on a stock portfolio. It's intended as a learning tool to help understand stock market analysis. It's not intended for real-world trading or financial use.
918
+ **Core Components:**
382
919
 
383
- How do I install and use SQA? You can install the gem by running gem install sqa or adding gem 'sqa' to your Gemfile and running bundle install. The project can be used from the command line or as a library in your own application. More details can be found in the README.md file.
920
+ - **`SQA::DataFrame`** - High-performance Polars-based data container for time series
921
+ - **`SQA::Stock`** - Represents a stock with price history and metadata
922
+ - **`SQA::Ticker`** - Stock symbol validation and lookup
923
+ - **`SQA::Strategy`** - Trading strategy execution framework
924
+ - **`SQA::Config`** - Configuration management
925
+ - **`SQAI`** - Alias for `SQA::TAI` (technical indicators from sqa-tai gem)
384
926
 
385
- What technical indicators does SQA support? The project includes a variety of technical indicators such as Average True Range, Bollinger Bands, Candlestick Patterns, Donchian Channel, Double Top Double Bottom Pattern, Exponential Moving Average, Fibonacci Retracement, Head and Shoulders Pattern, Elliott Wave Theory, Market Profile Analysis, Mean Reversion, Momentum, Moving Average Convergence Divergence, Peaks and Valleys, Predict Next Value, Relative Strength Index, Simple Moving Average, and Stochastic Oscillator.
927
+ **Advanced Components:**
386
928
 
387
- What data sources does SQA support? The project supports data from AlphaVantage and Yahoo Finance. You will need an API key for AlphaVantage to use this functionality.
929
+ - **`SQA::Portfolio`** - Position and trade tracking with P&L calculations
930
+ - **`SQA::Backtest`** - Strategy simulation with comprehensive metrics
931
+ - **`SQA::StrategyGenerator`** - Pattern mining from profitable historical trades
932
+ - **`SQA::GeneticProgram`** - Evolutionary algorithm for parameter optimization
933
+ - **`SQA::Strategy::KBS`** - RETE-based forward-chaining inference engine
934
+ - **`SQA::Stream`** - Real-time price stream processor with event callbacks
388
935
 
389
- What are the risks of using SQA for real-world trading? The madbomber/sqa project is intended as a learning tool and is not reliable for any kind of mission-critical financial use. The BUY/SELL signals that it generates should not be taken seriously. If you lose money in the stock market as a result of using this library, the project is not responsible.
936
+ **Data Flow:**
390
937
 
391
- Can I contribute to the SQA project? Yes, contributions are welcome. You can submit bug reports and pull requests on the GitHub repository.
938
+ 1. Create `SQA::Stock` with ticker symbol
939
+ 2. Stock fetches data from Alpha Vantage or Yahoo Finance
940
+ 3. Data stored in Polars-based `SQA::DataFrame`
941
+ 4. Apply technical indicators via `SQAI` / `SQA::TAI`
942
+ 5. Execute trading strategies to generate signals
943
+ 6. Analyze results with statistical functions
392
944
 
393
- What license does SQA use? The project is available as open source under the terms of the MIT License.
945
+ **Design Patterns:**
394
946
 
395
- What is the current state of the project? The project is a work in progress. The developer is experimenting with different gems to support various functionalities. Some features may not be fully implemented or may need further refinement.
947
+ - Plugin architecture for indicators and strategies
948
+ - Data source abstraction (Alpha Vantage, Yahoo Finance)
949
+ - Delegation to Polars for DataFrame operations
950
+ - Configuration hierarchy: defaults < environment variables < config file
396
951
 
952
+ ## Data Sources
397
953
 
398
- ## Other Similar Projects
954
+ ### Alpha Vantage
399
955
 
400
- There are several other (prehaps more mature) projects and libraries that provide similar capabilities to the SQA. Here are a few examples:
956
+ **Recommended data source** with a well-documented API.
401
957
 
402
- TA-Lib (Technical Analysis Library): This is a popular open-source software library that provides tools for technical analysis of financial markets. It includes over 150 functions for various types of analysis including pattern recognition, moving averages, oscillators, and more. It's available in several programming languages including Python, Java, Perl, and more. TA-Lib
958
+ - **URL:** [https://www.alphavantage.co/](https://www.alphavantage.co/)
959
+ - **API Key:** Required (free tier available)
960
+ - **Environment Variable:** `AV_API_KEY` or `ALPHAVANTAGE_API_KEY`
961
+ - **Rate Limits:** 5 calls/minute, 100 calls/day (free tier)
403
962
 
404
- Pandas TA: This is a Python library that provides comprehensive functionalities for technical analysis. It's an extension of the popular data analysis library Pandas and includes a wide range of financial indicators. Pandas TA
963
+ ```ruby
964
+ stock = SQA::Stock.new(ticker: 'GOOGL', source: :alpha_vantage)
965
+ ```
405
966
 
406
- Backtrader: This is a Python library for backtesting trading strategies. It supports a wide range of trading concepts including trading calendars, multiple data feeds, and order execution types. Backtrader
967
+ ### Yahoo Finance
407
968
 
408
- PyAlgoTrade: This is another Python library for backtesting stock trading strategies. It supports Yahoo! Finance, Google Finance, and others as data sources, and has a focus on simplicity and flexibility. PyAlgoTrade
969
+ **No API available** - uses web scraping for historical data.
409
970
 
410
- QuantLib: This is a comprehensive software framework for quantitative finance, it provides tools for many aspects of quantitative finance including trading and risk management, financial instruments, mathematics, numerical methods, and model calibration. QuantLib
971
+ - **URL:** [https://finance.yahoo.com/](https://finance.yahoo.com/)
972
+ - **Manual Download:** Download CSV files and place in `SQA.data_dir`
973
+ - **Filename Format:** `ticker.csv` (lowercase), e.g., `aapl.csv`
411
974
 
412
- zipline: This is a Python library for algorithmic trading. It allows strategy testing and supports live-trading and backtesting, and includes a number of financial computations to aid in trading decisions. zipline
975
+ To manually download:
976
+ 1. Visit [https://finance.yahoo.com/quote/AAPL/history?p=AAPL](https://finance.yahoo.com/quote/AAPL/history?p=AAPL)
977
+ 2. Download historical data as CSV
978
+ 3. Move to your data directory as `aapl.csv`
413
979
 
980
+ ```ruby
981
+ stock = SQA::Stock.new(ticker: 'AAPL', source: :yahoo_finance)
982
+ ```
414
983
 
415
984
  ## Contributing
416
985
 
417
- I can always use some help on this stuff. Got an idea for a new indicator or strategy? Want to improve the math? Make the signals better? Let's collaborate!
986
+ Contributions are welcome! Got an idea for a new indicator or strategy? Want to improve the math or signals?
418
987
 
419
- Bug reports and pull requests are welcome on GitHub at https://github.com/MadBomber/sqa.
988
+ - **Bug reports and pull requests:** [https://github.com/MadBomber/sqa](https://github.com/MadBomber/sqa)
989
+ - **Technical indicators:** Contribute to [sqa-tai](https://github.com/MadBomber/sqa-tai)
420
990
 
421
991
  ## License
422
992
 
423
993
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
994
+
995
+ ---
996
+
997
+ **Remember:** This is an educational tool. Historical performance is not an indicator of future results. Never use this for real trading decisions.