sqa 0.0.24 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.goose/memory/development.txt +3 -0
- data/.semver +6 -0
- data/ARCHITECTURE.md +648 -0
- data/CHANGELOG.md +95 -0
- data/CLAUDE.md +674 -0
- data/COMMITS.md +196 -0
- data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
- data/NEXT-STEPS.md +154 -0
- data/README.md +839 -265
- data/TASKS.md +358 -0
- data/TEST_RESULTS.md +140 -0
- data/TODO.md +42 -0
- data/_notes.txt +25 -0
- data/bin/sqa-console +11 -0
- data/data/talk_talk.json +103284 -0
- data/develop_summary.md +313 -0
- data/docs/advanced/backtesting.md +206 -0
- data/docs/advanced/ensemble.md +68 -0
- data/docs/advanced/fpop.md +153 -0
- data/docs/advanced/index.md +112 -0
- data/docs/advanced/multi-timeframe.md +67 -0
- data/docs/advanced/pattern-matcher.md +75 -0
- data/docs/advanced/portfolio-optimizer.md +79 -0
- data/docs/advanced/portfolio.md +166 -0
- data/docs/advanced/risk-management.md +210 -0
- data/docs/advanced/strategy-generator.md +158 -0
- data/docs/advanced/streaming.md +209 -0
- data/docs/ai_and_ml.md +80 -0
- data/docs/api/dataframe.md +1114 -0
- data/docs/api/index.md +126 -0
- data/docs/assets/css/custom.css +88 -0
- data/docs/assets/images/sqa.jpg +0 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/concepts/index.md +60 -0
- data/docs/contributing/index.md +60 -0
- data/docs/data-sources/index.md +66 -0
- data/docs/data_frame.md +316 -97
- data/docs/factors_that_impact_price.md +26 -0
- data/docs/finviz.md +11 -0
- data/docs/fx_pro_bit.md +25 -0
- data/docs/genetic_programming.md +104 -0
- data/docs/getting-started/index.md +107 -0
- data/docs/getting-started/installation.md +229 -0
- data/docs/getting-started/quick-start.md +244 -0
- data/docs/i_gotta_an_idea.md +22 -0
- data/docs/index.md +161 -0
- data/docs/indicators/index.md +97 -0
- data/docs/indicators.md +110 -24
- data/docs/options.md +8 -0
- data/docs/strategies/bollinger-bands.md +146 -0
- data/docs/strategies/consensus.md +64 -0
- data/docs/strategies/custom.md +310 -0
- data/docs/strategies/ema.md +53 -0
- data/docs/strategies/index.md +92 -0
- data/docs/strategies/kbs.md +164 -0
- data/docs/strategies/macd.md +96 -0
- data/docs/strategies/market-profile.md +54 -0
- data/docs/strategies/mean-reversion.md +58 -0
- data/docs/strategies/rsi.md +95 -0
- data/docs/strategies/sma.md +55 -0
- data/docs/strategies/stochastic.md +63 -0
- data/docs/strategies/volume-breakout.md +54 -0
- data/docs/tags.md +7 -0
- data/examples/README.md +354 -0
- data/examples/advanced_features_example.rb +350 -0
- data/examples/fpop_analysis_example.rb +191 -0
- data/examples/genetic_programming_example.rb +148 -0
- data/examples/kbs_strategy_example.rb +208 -0
- data/examples/pattern_context_example.rb +300 -0
- data/examples/rails_app/Gemfile +34 -0
- data/examples/rails_app/README.md +416 -0
- data/examples/rails_app/app/assets/javascripts/application.js +107 -0
- data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
- data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
- data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
- data/examples/rails_app/app/controllers/application_controller.rb +22 -0
- data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
- data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
- data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
- data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
- data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
- data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
- data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
- data/examples/rails_app/app/views/errors/show.html.erb +17 -0
- data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
- data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
- data/examples/rails_app/bin/rails +6 -0
- data/examples/rails_app/config/application.rb +45 -0
- data/examples/rails_app/config/boot.rb +5 -0
- data/examples/rails_app/config/database.yml +18 -0
- data/examples/rails_app/config/environment.rb +11 -0
- data/examples/rails_app/config/routes.rb +26 -0
- data/examples/rails_app/config.ru +8 -0
- data/examples/realtime_stream_example.rb +274 -0
- data/examples/sinatra_app/Gemfile +42 -0
- data/examples/sinatra_app/Gemfile.lock +268 -0
- data/examples/sinatra_app/QUICKSTART.md +169 -0
- data/examples/sinatra_app/README.md +471 -0
- data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +90 -0
- data/examples/sinatra_app/TROUBLESHOOTING.md +95 -0
- data/examples/sinatra_app/app.rb +404 -0
- data/examples/sinatra_app/config.ru +5 -0
- data/examples/sinatra_app/public/css/style.css +723 -0
- data/examples/sinatra_app/public/debug_macd.html +82 -0
- data/examples/sinatra_app/public/js/app.js +107 -0
- data/examples/sinatra_app/start.sh +53 -0
- data/examples/sinatra_app/views/analyze.erb +306 -0
- data/examples/sinatra_app/views/backtest.erb +325 -0
- data/examples/sinatra_app/views/dashboard.erb +831 -0
- data/examples/sinatra_app/views/error.erb +58 -0
- data/examples/sinatra_app/views/index.erb +118 -0
- data/examples/sinatra_app/views/layout.erb +61 -0
- data/examples/sinatra_app/views/portfolio.erb +43 -0
- data/examples/strategy_generator_example.rb +346 -0
- data/hsa_portfolio.csv +11 -0
- data/justfile +0 -0
- data/lib/api/alpha_vantage_api.rb +462 -0
- data/lib/sqa/backtest.rb +329 -0
- data/lib/sqa/data_frame/alpha_vantage.rb +51 -63
- data/lib/sqa/data_frame/data.rb +92 -0
- data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
- data/lib/sqa/data_frame.rb +154 -243
- data/lib/sqa/ensemble.rb +359 -0
- data/lib/sqa/fpop.rb +199 -0
- data/lib/sqa/gp.rb +259 -0
- data/lib/sqa/indicator.rb +16 -6
- data/lib/sqa/init.rb +15 -8
- data/lib/sqa/market_regime.rb +240 -0
- data/lib/sqa/multi_timeframe.rb +379 -0
- data/lib/sqa/pattern_matcher.rb +497 -0
- data/lib/sqa/portfolio.rb +260 -6
- data/lib/sqa/portfolio_optimizer.rb +377 -0
- data/lib/sqa/risk_manager.rb +442 -0
- data/lib/sqa/seasonal_analyzer.rb +209 -0
- data/lib/sqa/sector_analyzer.rb +300 -0
- data/lib/sqa/stock.rb +131 -127
- data/lib/sqa/strategy/bollinger_bands.rb +42 -0
- data/lib/sqa/strategy/consensus.rb +5 -2
- data/lib/sqa/strategy/kbs_strategy.rb +470 -0
- data/lib/sqa/strategy/macd.rb +46 -0
- data/lib/sqa/strategy/mp.rb +1 -1
- data/lib/sqa/strategy/stochastic.rb +60 -0
- data/lib/sqa/strategy/volume_breakout.rb +57 -0
- data/lib/sqa/strategy.rb +5 -0
- data/lib/sqa/strategy_generator.rb +947 -0
- data/lib/sqa/stream.rb +361 -0
- data/lib/sqa/ticker.rb +9 -2
- data/lib/sqa/version.rb +1 -7
- data/lib/sqa.rb +35 -20
- data/main.just +81 -0
- data/mkdocs.yml +252 -0
- data/trace.log +0 -0
- metadata +265 -69
- data/bin/sqa +0 -6
- data/docs/alpha_vantage_technical_indicators.md +0 -62
- data/docs/average_true_range.md +0 -9
- data/docs/bollinger_bands.md +0 -15
- data/docs/candlestick_pattern_recognizer.md +0 -4
- data/docs/donchian_channel.md +0 -5
- data/docs/double_top_bottom_pattern.md +0 -3
- data/docs/exponential_moving_average.md +0 -19
- data/docs/fibonacci_retracement.md +0 -30
- data/docs/head_and_shoulders_pattern.md +0 -3
- data/docs/market_profile.md +0 -4
- data/docs/momentum.md +0 -19
- data/docs/moving_average_convergence_divergence.md +0 -23
- data/docs/peaks_and_valleys.md +0 -11
- data/docs/relative_strength_index.md +0 -6
- data/docs/simple_moving_average.md +0 -8
- data/docs/stochastic_oscillator.md +0 -4
- data/docs/ta_lib.md +0 -160
- data/docs/true_range.md +0 -12
- data/lib/patches/dry-cli.rb +0 -228
- data/lib/sqa/activity.rb +0 -10
- data/lib/sqa/cli.rb +0 -62
- data/lib/sqa/commands/analysis.rb +0 -309
- data/lib/sqa/commands/base.rb +0 -139
- data/lib/sqa/commands/web.rb +0 -199
- data/lib/sqa/commands.rb +0 -22
- data/lib/sqa/constants.rb +0 -23
- data/lib/sqa/indicator/average_true_range.rb +0 -33
- data/lib/sqa/indicator/bollinger_bands.rb +0 -28
- data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
- data/lib/sqa/indicator/donchian_channel.rb +0 -29
- data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
- data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
- data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
- data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
- data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
- data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
- data/lib/sqa/indicator/market_profile.rb +0 -32
- data/lib/sqa/indicator/mean_reversion.rb +0 -37
- data/lib/sqa/indicator/momentum.rb +0 -28
- data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
- data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
- data/lib/sqa/indicator/predict_next_value.rb +0 -202
- data/lib/sqa/indicator/relative_strength_index.rb +0 -47
- data/lib/sqa/indicator/simple_moving_average.rb +0 -24
- data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
- data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
- data/lib/sqa/indicator/true_range.rb +0 -39
- data/lib/sqa/trade.rb +0 -26
data/README.md
CHANGED
|
@@ -1,423 +1,997 @@
|
|
|
1
|
-
|
|
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
|
-
[
|
|
52
|
+
<!-- Tocer[start]: Auto-generated, don't remove. -->
|
|
4
53
|
|
|
5
|
-
|
|
54
|
+
## Table of Contents
|
|
6
55
|
|
|
7
|
-
|
|
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
|
-
<!--
|
|
75
|
+
<!-- Tocer[finish]: Auto-generated, don't remove. -->
|
|
76
|
+
## Installation
|
|
10
77
|
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
80
|
+
```bash
|
|
81
|
+
gem install sqa
|
|
82
|
+
```
|
|
16
83
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
88
|
+
```ruby
|
|
89
|
+
gem 'sqa'
|
|
90
|
+
```
|
|
25
91
|
|
|
26
|
-
|
|
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
|
-
|
|
104
|
+
## Configuration
|
|
34
105
|
|
|
35
|
-
|
|
106
|
+
### Data Directory
|
|
36
107
|
|
|
37
|
-
|
|
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
|
-
|
|
110
|
+
```ruby
|
|
111
|
+
require 'sqa'
|
|
62
112
|
|
|
63
|
-
|
|
113
|
+
# Initialize with default configuration
|
|
114
|
+
SQA.init
|
|
64
115
|
|
|
65
|
-
|
|
116
|
+
# Or specify a custom data directory
|
|
117
|
+
SQA::Config.new(data_dir: '~/Documents/my_stock_data')
|
|
118
|
+
```
|
|
66
119
|
|
|
67
|
-
|
|
120
|
+
Create your data directory:
|
|
68
121
|
|
|
69
|
-
|
|
122
|
+
```bash
|
|
123
|
+
mkdir ~/sqa_data
|
|
124
|
+
```
|
|
70
125
|
|
|
71
|
-
|
|
126
|
+
### Alpha Vantage API Key
|
|
72
127
|
|
|
73
|
-
|
|
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
|
-
|
|
130
|
+
Set the environment variable:
|
|
76
131
|
|
|
77
|
-
|
|
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
|
-
|
|
138
|
+
The free tier allows:
|
|
139
|
+
- 5 API calls per minute
|
|
140
|
+
- 100 API calls per day
|
|
80
141
|
|
|
81
|
-
##
|
|
142
|
+
## Usage
|
|
82
143
|
|
|
83
|
-
|
|
144
|
+
### Working with Stocks
|
|
84
145
|
|
|
85
146
|
```ruby
|
|
86
|
-
|
|
87
|
-
sqa --version
|
|
147
|
+
require 'sqa'
|
|
88
148
|
|
|
89
|
-
#
|
|
149
|
+
# Initialize SQA
|
|
150
|
+
SQA.init
|
|
90
151
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```
|
|
156
|
+
# Access the DataFrame
|
|
157
|
+
aapl.df
|
|
158
|
+
#=> SQA::DataFrame (wraps Polars::DataFrame)
|
|
98
159
|
|
|
99
|
-
|
|
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
|
-
|
|
164
|
+
# Access price data
|
|
165
|
+
aapl.df["adj_close_price"]
|
|
166
|
+
#=> Polars::Series with all adjusted closing prices
|
|
102
167
|
|
|
103
|
-
|
|
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
|
-
|
|
173
|
+
# Access company metadata
|
|
174
|
+
aapl.name
|
|
175
|
+
#=> "Apple Inc."
|
|
106
176
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Stock Quantitative Analysis (SQA)
|
|
177
|
+
aapl.exchange
|
|
178
|
+
#=> "NASDAQ"
|
|
110
179
|
|
|
111
|
-
|
|
180
|
+
aapl.overview
|
|
181
|
+
#=> Hash with detailed company information
|
|
182
|
+
```
|
|
112
183
|
|
|
113
|
-
|
|
184
|
+
### DataFrame Operations
|
|
114
185
|
|
|
115
|
-
|
|
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
|
-
|
|
130
|
-
|
|
188
|
+
```ruby
|
|
189
|
+
require 'sqa'
|
|
131
190
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
198
|
+
# Load from CSV
|
|
199
|
+
df = SQA::DataFrame.load(source: 'aapl.csv')
|
|
138
200
|
|
|
139
|
-
|
|
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
|
-
|
|
207
|
+
# Access underlying Polars DataFrame
|
|
208
|
+
df.data # Polars::DataFrame
|
|
209
|
+
```
|
|
142
210
|
|
|
143
|
-
|
|
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
|
-
|
|
147
|
-
mkdir ~/Documents/sqa_data
|
|
148
|
-
sqa --data-dir ~/Documents/sqa_data --dump-config ~/.sqa.yml
|
|
149
|
-
```
|
|
216
|
+
require 'sqa'
|
|
150
217
|
|
|
151
|
-
|
|
218
|
+
prices = [100, 102, 105, 103, 107, 110, 108, 112, 115, 113]
|
|
152
219
|
|
|
153
|
-
|
|
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
|
-
|
|
224
|
+
# Relative Strength Index
|
|
225
|
+
rsi = SQAI.rsi(prices, period: 14)
|
|
226
|
+
#=> [70.5, 68.2, ...]
|
|
156
227
|
|
|
157
|
-
|
|
228
|
+
# Exponential Moving Average
|
|
229
|
+
ema = SQAI.ema(prices, period: 5)
|
|
230
|
+
#=> [...]
|
|
158
231
|
|
|
159
|
-
|
|
232
|
+
# Bollinger Bands (returns upper, middle, lower)
|
|
233
|
+
upper, middle, lower = SQAI.bbands(prices, period: 5)
|
|
160
234
|
|
|
161
|
-
|
|
235
|
+
# MACD
|
|
236
|
+
macd, signal, histogram = SQAI.macd(prices, fast_period: 12, slow_period: 26, signal_period: 9)
|
|
162
237
|
|
|
163
|
-
|
|
238
|
+
# Many more indicators available!
|
|
239
|
+
# See: https://github.com/MadBomber/sqa-tai
|
|
240
|
+
```
|
|
164
241
|
|
|
165
|
-
|
|
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
|
-
|
|
246
|
+
Build and test trading strategies:
|
|
169
247
|
|
|
170
|
-
|
|
248
|
+
```ruby
|
|
249
|
+
require 'sqa'
|
|
250
|
+
require 'ostruct'
|
|
171
251
|
|
|
172
|
-
|
|
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
|
-
|
|
288
|
+
### Statistics
|
|
175
289
|
|
|
176
|
-
|
|
290
|
+
Comprehensive statistical analysis on price data (via `lite-statistics` gem):
|
|
177
291
|
|
|
178
|
-
|
|
292
|
+
```ruby
|
|
293
|
+
require 'sqa'
|
|
179
294
|
|
|
180
|
-
|
|
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
|
-
|
|
311
|
+
### Portfolio Management
|
|
183
312
|
|
|
184
|
-
|
|
313
|
+
Track positions, trades, and P&L:
|
|
185
314
|
|
|
186
|
-
|
|
315
|
+
```ruby
|
|
316
|
+
require 'sqa'
|
|
187
317
|
|
|
188
|
-
|
|
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
|
-
|
|
324
|
+
# Sell stock
|
|
325
|
+
portfolio.sell('AAPL', shares: 5, price: 160.0, date: Date.today)
|
|
192
326
|
|
|
193
|
-
|
|
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
|
-
|
|
336
|
+
# View summary
|
|
337
|
+
portfolio.summary
|
|
338
|
+
#=> { initial_cash: 10000.0, cash: 8248.0, positions: 1, trades: 2, ... }
|
|
197
339
|
|
|
198
|
-
|
|
340
|
+
# Save trades to CSV
|
|
341
|
+
portfolio.save_to_csv('my_trades.csv')
|
|
199
342
|
|
|
200
|
-
|
|
343
|
+
# Load from CSV
|
|
344
|
+
portfolio.load_from_csv('my_trades.csv')
|
|
345
|
+
```
|
|
201
346
|
|
|
202
|
-
|
|
347
|
+
### Backtesting
|
|
203
348
|
|
|
204
|
-
|
|
349
|
+
Simulate trading strategies against historical data:
|
|
205
350
|
|
|
206
|
-
|
|
351
|
+
```ruby
|
|
352
|
+
require 'sqa'
|
|
207
353
|
|
|
208
|
-
|
|
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
|
-
|
|
381
|
+
### Strategy Generator
|
|
211
382
|
|
|
212
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
423
|
+
### Genetic Programming
|
|
424
|
+
|
|
425
|
+
Evolve optimal strategy parameters through natural selection:
|
|
231
426
|
|
|
232
427
|
```ruby
|
|
233
|
-
|
|
234
|
-
#=> [:timestamp, :open_price, :high_price, :low_price, :close_price, :adj_close_price, :volume]
|
|
428
|
+
require 'sqa'
|
|
235
429
|
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
473
|
+
### Knowledge-Based Strategy (KBS)
|
|
474
|
+
|
|
475
|
+
Build sophisticated rule-based systems with RETE forward chaining:
|
|
241
476
|
|
|
242
477
|
```ruby
|
|
243
|
-
|
|
244
|
-
#=> [:ticker, :source, :indicators, :overview]
|
|
478
|
+
require 'sqa'
|
|
245
479
|
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
565
|
+
### FPL Analysis (Future Period Loss/Profit)
|
|
255
566
|
|
|
256
|
-
|
|
567
|
+
Analyze potential future price movements with risk metrics:
|
|
257
568
|
|
|
258
569
|
```ruby
|
|
259
|
-
require '
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
:sample_standard_error => 0.532049621745943,
|
|
297
|
-
:sample_variance => 1.415384000000005,
|
|
298
|
-
:sample_zscores => {
|
|
299
|
-
179.8 => 0.6337736880639895,
|
|
300
|
-
180.71 => 1.3986729667618856,
|
|
301
|
-
178.85 => -0.16474753695031497,
|
|
302
|
-
178.72 => -0.2740188624785824,
|
|
303
|
-
177.15 => -1.5936802553969298
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
#=> nil
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
-
|
|
320
|
-
|
|
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
|
-
|
|
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
|
-
|
|
718
|
+
Analyze patterns across related stocks using KBS blackboards:
|
|
326
719
|
|
|
327
720
|
```ruby
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
# that is defined in each strategy added
|
|
342
|
-
ap ss.strategies #=>∑
|
|
343
|
-
[
|
|
344
|
-
[0] SQA::Strategy::Random#trade(vector),
|
|
345
|
-
[1] SQA::Strategy::RSI#trade(vector)
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
880
|
+
## Interactive Console
|
|
358
881
|
|
|
359
|
-
|
|
882
|
+
Launch an interactive Ruby console with SQA loaded:
|
|
360
883
|
|
|
361
|
-
|
|
884
|
+
```bash
|
|
885
|
+
sqa-console
|
|
886
|
+
```
|
|
362
887
|
|
|
363
|
-
|
|
888
|
+
This opens IRB with the SQA library pre-loaded, allowing you to experiment interactively:
|
|
364
889
|
|
|
365
|
-
|
|
890
|
+
```ruby
|
|
891
|
+
# Already loaded: require 'sqa'
|
|
366
892
|
|
|
367
|
-
|
|
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
|
-
|
|
899
|
+
## Examples
|
|
370
900
|
|
|
371
|
-
|
|
901
|
+
The `examples/` directory contains comprehensive demonstrations of advanced features:
|
|
372
902
|
|
|
373
|
-
|
|
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
|
-
|
|
908
|
+
Run examples:
|
|
909
|
+
```bash
|
|
910
|
+
ruby examples/genetic_programming_example.rb
|
|
911
|
+
ruby examples/strategy_generator_example.rb
|
|
912
|
+
```
|
|
376
913
|
|
|
377
|
-
|
|
914
|
+
See `examples/README.md` for detailed documentation and integration patterns.
|
|
378
915
|
|
|
379
|
-
##
|
|
916
|
+
## Architecture
|
|
380
917
|
|
|
381
|
-
|
|
918
|
+
**Core Components:**
|
|
382
919
|
|
|
383
|
-
|
|
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
|
-
|
|
927
|
+
**Advanced Components:**
|
|
386
928
|
|
|
387
|
-
|
|
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
|
-
|
|
936
|
+
**Data Flow:**
|
|
390
937
|
|
|
391
|
-
|
|
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
|
-
|
|
945
|
+
**Design Patterns:**
|
|
394
946
|
|
|
395
|
-
|
|
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
|
-
|
|
954
|
+
### Alpha Vantage
|
|
399
955
|
|
|
400
|
-
|
|
956
|
+
**Recommended data source** with a well-documented API.
|
|
401
957
|
|
|
402
|
-
|
|
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
|
-
|
|
963
|
+
```ruby
|
|
964
|
+
stock = SQA::Stock.new(ticker: 'GOOGL', source: :alpha_vantage)
|
|
965
|
+
```
|
|
405
966
|
|
|
406
|
-
|
|
967
|
+
### Yahoo Finance
|
|
407
968
|
|
|
408
|
-
|
|
969
|
+
**No API available** - uses web scraping for historical data.
|
|
409
970
|
|
|
410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|