sqa 0.0.32 → 0.0.38
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/CHANGELOG.md +154 -1
- data/README.md +4 -0
- data/Rakefile +52 -10
- data/docs/advanced/index.md +1 -13
- data/docs/api/index.md +547 -61
- data/docs/api-reference/alphavantageapi.md +1057 -0
- data/docs/api-reference/apierror.md +31 -0
- data/docs/api-reference/index.md +221 -0
- data/docs/api-reference/notimplemented.md +27 -0
- data/docs/api-reference/sqa.md +267 -0
- data/docs/api-reference/sqa_backtest.md +171 -0
- data/docs/api-reference/sqa_backtest_results.md +530 -0
- data/docs/api-reference/sqa_badparametererror.md +13 -0
- data/docs/api-reference/sqa_config.md +538 -0
- data/docs/api-reference/sqa_configurationerror.md +13 -0
- data/docs/api-reference/sqa_datafetcherror.md +56 -0
- data/docs/api-reference/sqa_dataframe.md +779 -0
- data/docs/api-reference/sqa_dataframe_alphavantage.md +30 -0
- data/docs/api-reference/sqa_dataframe_data.md +325 -0
- data/docs/api-reference/sqa_dataframe_yahoofinance.md +25 -0
- data/docs/api-reference/sqa_ensemble.md +413 -0
- data/docs/api-reference/sqa_fpop.md +211 -0
- data/docs/api-reference/sqa_geneticprogram.md +325 -0
- data/docs/api-reference/sqa_geneticprogram_individual.md +114 -0
- data/docs/api-reference/sqa_marketregime.md +212 -0
- data/docs/api-reference/sqa_multitimeframe.md +227 -0
- data/docs/api-reference/sqa_patternmatcher.md +195 -0
- data/docs/api-reference/sqa_pluginmanager.md +55 -0
- data/docs/api-reference/sqa_portfolio.md +512 -0
- data/docs/api-reference/sqa_portfolio_position.md +220 -0
- data/docs/api-reference/sqa_portfolio_trade.md +332 -0
- data/docs/api-reference/sqa_portfoliooptimizer.md +248 -0
- data/docs/api-reference/sqa_riskmanager.md +388 -0
- data/docs/api-reference/sqa_seasonalanalyzer.md +121 -0
- data/docs/api-reference/sqa_sectoranalyzer.md +163 -0
- data/docs/api-reference/sqa_stock.md +661 -0
- data/docs/api-reference/sqa_strategy.md +178 -0
- data/docs/api-reference/sqa_strategy_bollingerbands.md +26 -0
- data/docs/api-reference/sqa_strategy_common.md +29 -0
- data/docs/api-reference/sqa_strategy_consensus.md +129 -0
- data/docs/api-reference/sqa_strategy_ema.md +41 -0
- data/docs/api-reference/sqa_strategy_kbs.md +154 -0
- data/docs/api-reference/sqa_strategy_macd.md +26 -0
- data/docs/api-reference/sqa_strategy_mp.md +41 -0
- data/docs/api-reference/sqa_strategy_mr.md +41 -0
- data/docs/api-reference/sqa_strategy_random.md +41 -0
- data/docs/api-reference/sqa_strategy_rsi.md +41 -0
- data/docs/api-reference/sqa_strategy_sma.md +41 -0
- data/docs/api-reference/sqa_strategy_stochastic.md +26 -0
- data/docs/api-reference/sqa_strategy_volumebreakout.md +26 -0
- data/docs/api-reference/sqa_strategygenerator.md +298 -0
- data/docs/api-reference/sqa_strategygenerator_pattern.md +264 -0
- data/docs/api-reference/sqa_strategygenerator_patterncontext.md +326 -0
- data/docs/api-reference/sqa_strategygenerator_profitablepoint.md +424 -0
- data/docs/api-reference/sqa_stream.md +256 -0
- data/docs/api-reference/sqa_ticker.md +175 -0
- data/docs/api-reference/string.md +135 -0
- data/docs/assets/images/advanced-workflow.svg +89 -0
- data/docs/assets/images/architecture.svg +107 -0
- data/docs/assets/images/data-flow.svg +138 -0
- data/docs/assets/images/getting-started-workflow.svg +88 -0
- data/docs/assets/images/strategy-flow.svg +78 -0
- data/docs/assets/images/system-architecture.svg +150 -0
- data/docs/concepts/index.md +292 -19
- data/docs/file_formats.md +250 -0
- data/docs/getting-started/index.md +1 -14
- data/docs/index.md +26 -23
- data/docs/llms.txt +109 -0
- data/docs/strategies/kbs.md +15 -14
- data/docs/strategy.md +381 -3
- data/docs/terms_of_use.md +1 -1
- data/examples/README.md +10 -0
- data/lib/api/alpha_vantage_api.rb +3 -7
- data/lib/sqa/backtest.rb +32 -0
- data/lib/sqa/config.rb +109 -28
- data/lib/sqa/data_frame/data.rb +13 -1
- data/lib/sqa/data_frame.rb +193 -26
- data/lib/sqa/errors.rb +79 -17
- data/lib/sqa/init.rb +70 -15
- data/lib/sqa/pattern_matcher.rb +4 -4
- data/lib/sqa/portfolio.rb +55 -1
- data/lib/sqa/sector_analyzer.rb +3 -11
- data/lib/sqa/stock.rb +180 -15
- data/lib/sqa/strategy.rb +62 -4
- data/lib/sqa/ticker.rb +106 -48
- data/lib/sqa/version.rb +1 -1
- data/lib/sqa.rb +4 -4
- data/mkdocs.yml +69 -81
- metadata +89 -21
- data/docs/README.md +0 -43
- data/examples/sinatra_app/Gemfile +0 -42
- data/examples/sinatra_app/Gemfile.lock +0 -268
- data/examples/sinatra_app/QUICKSTART.md +0 -169
- data/examples/sinatra_app/README.md +0 -471
- data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +0 -90
- data/examples/sinatra_app/TROUBLESHOOTING.md +0 -95
- data/examples/sinatra_app/app.rb +0 -404
- data/examples/sinatra_app/config.ru +0 -5
- data/examples/sinatra_app/public/css/style.css +0 -723
- data/examples/sinatra_app/public/debug_macd.html +0 -82
- data/examples/sinatra_app/public/js/app.js +0 -107
- data/examples/sinatra_app/start.sh +0 -53
- data/examples/sinatra_app/views/analyze.erb +0 -306
- data/examples/sinatra_app/views/backtest.erb +0 -325
- data/examples/sinatra_app/views/dashboard.erb +0 -831
- data/examples/sinatra_app/views/error.erb +0 -58
- data/examples/sinatra_app/views/index.erb +0 -118
- data/examples/sinatra_app/views/layout.erb +0 -61
- data/examples/sinatra_app/views/portfolio.erb +0 -43
data/docs/api/index.md
CHANGED
|
@@ -2,125 +2,611 @@
|
|
|
2
2
|
|
|
3
3
|
Complete API documentation for SQA classes and modules.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
!!! tip "Auto-Generated API Documentation"
|
|
6
|
+
For detailed API documentation with all methods, parameters, and return values, see the **[Complete API Reference](../api-reference/index.md)** - automatically generated from YARD comments with the same Material theme!
|
|
7
|
+
|
|
8
|
+
## Quick Navigation
|
|
9
|
+
|
|
10
|
+
| Class | Description |
|
|
11
|
+
|-------|-------------|
|
|
12
|
+
| [SQA Module](#sqa-module) | Main module, initialization, configuration |
|
|
13
|
+
| [SQA::Stock](#sqastock) | Stock data management |
|
|
14
|
+
| [SQA::DataFrame](#sqadataframe) | High-performance data structures |
|
|
15
|
+
| [SQA::Strategy](#sqastrategy) | Trading strategy framework |
|
|
16
|
+
| [SQA::Portfolio](#sqaportfolio) | Position and trade tracking |
|
|
17
|
+
| [SQA::Backtest](#sqabacktest) | Strategy simulation |
|
|
18
|
+
| [SQA::Config](#sqaconfig) | Configuration management |
|
|
19
|
+
| [SQAI/SQA::TAI](#sqai-indicators) | Technical indicators |
|
|
6
20
|
|
|
7
|
-
|
|
8
|
-
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## SQA Module
|
|
24
|
+
|
|
25
|
+
Main module containing initialization and global accessors.
|
|
26
|
+
|
|
27
|
+
### Module Methods
|
|
28
|
+
|
|
29
|
+
#### `SQA.init(argv = ARGV)`
|
|
30
|
+
|
|
31
|
+
Initializes the SQA library. Should be called once at application startup.
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
SQA.init
|
|
35
|
+
# Or with arguments
|
|
36
|
+
SQA.init("--debug")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Returns:** `SQA::Config` - The configuration instance
|
|
40
|
+
|
|
41
|
+
#### `SQA.config`
|
|
42
|
+
|
|
43
|
+
Returns the current configuration object.
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
SQA.config.data_dir = "~/my_data"
|
|
47
|
+
SQA.config.debug = true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Returns:** `SQA::Config`
|
|
51
|
+
|
|
52
|
+
#### `SQA.data_dir`
|
|
53
|
+
|
|
54
|
+
Returns the data directory as a Pathname.
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
path = SQA.data_dir # => #<Pathname:/Users/you/sqa_data>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Returns:** `Pathname`
|
|
61
|
+
|
|
62
|
+
#### `SQA.av_api_key`
|
|
63
|
+
|
|
64
|
+
Returns the Alpha Vantage API key from environment variables.
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
key = SQA.av_api_key
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Returns:** `String`
|
|
71
|
+
**Raises:** `SQA::ConfigurationError` if not set
|
|
72
|
+
|
|
73
|
+
#### `SQA.debug?` / `SQA.verbose?`
|
|
74
|
+
|
|
75
|
+
Check if debug or verbose mode is enabled.
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
puts "Debug mode" if SQA.debug?
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Returns:** `Boolean`
|
|
82
|
+
|
|
83
|
+
---
|
|
9
84
|
|
|
10
|
-
|
|
11
|
-
- `SQA.config` - Access configuration
|
|
12
|
-
- `SQA.data_dir` - Get data directory
|
|
13
|
-
- `SQA.av` - Alpha Vantage API client
|
|
85
|
+
## SQA::Stock
|
|
14
86
|
|
|
15
|
-
|
|
16
|
-
|
|
87
|
+
Represents a stock with historical data and metadata.
|
|
88
|
+
|
|
89
|
+
### Constructor
|
|
90
|
+
|
|
91
|
+
#### `SQA::Stock.new(ticker:, source: :alpha_vantage)`
|
|
92
|
+
|
|
93
|
+
Creates a new Stock instance and loads/fetches its data.
|
|
17
94
|
|
|
18
95
|
```ruby
|
|
19
96
|
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
97
|
+
stock = SQA::Stock.new(ticker: 'MSFT', source: :yahoo_finance)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Parameters:**
|
|
101
|
+
- `ticker` (String) - Stock ticker symbol
|
|
102
|
+
- `source` (Symbol) - Data source (`:alpha_vantage` or `:yahoo_finance`)
|
|
103
|
+
|
|
104
|
+
**Raises:** `SQA::DataFetchError` if data cannot be fetched
|
|
105
|
+
|
|
106
|
+
### Instance Attributes
|
|
107
|
+
|
|
108
|
+
| Attribute | Type | Description |
|
|
109
|
+
|-----------|------|-------------|
|
|
110
|
+
| `ticker` | String | Stock symbol (lowercase) |
|
|
111
|
+
| `name` | String | Company name |
|
|
112
|
+
| `exchange` | String | Exchange (NASDAQ, NYSE, etc.) |
|
|
113
|
+
| `source` | Symbol | Data source used |
|
|
114
|
+
| `df` | SQA::DataFrame | Price/volume DataFrame |
|
|
115
|
+
| `data` | SQA::DataFrame::Data | Stock metadata |
|
|
116
|
+
| `indicators` | Hash | Cached indicator values |
|
|
117
|
+
| `overview` | Hash | Company overview from API |
|
|
118
|
+
|
|
119
|
+
### Instance Methods
|
|
120
|
+
|
|
121
|
+
#### `#to_s` / `#inspect`
|
|
122
|
+
|
|
123
|
+
Returns human-readable string representation.
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
stock.to_s # => "aapl with 1258 data points from 2019-11-09 to 2024-11-08"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### `#update`
|
|
130
|
+
|
|
131
|
+
Updates stock's overview data from API.
|
|
132
|
+
|
|
133
|
+
#### `#save_data`
|
|
134
|
+
|
|
135
|
+
Persists metadata to JSON file.
|
|
136
|
+
|
|
137
|
+
### Class Methods
|
|
138
|
+
|
|
139
|
+
#### `SQA::Stock.top`
|
|
140
|
+
|
|
141
|
+
Fetches top gainers, losers, and most actively traded stocks.
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
top = SQA::Stock.top
|
|
145
|
+
top.top_gainers.each { |s| puts s.ticker }
|
|
146
|
+
top.top_losers.first
|
|
147
|
+
top.most_actively_traded
|
|
20
148
|
```
|
|
21
149
|
|
|
22
|
-
**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
150
|
+
**Returns:** `Hashie::Mash` with three arrays
|
|
151
|
+
|
|
152
|
+
#### `SQA::Stock.connection` / `SQA::Stock.connection=`
|
|
153
|
+
|
|
154
|
+
Get or set the Faraday connection for API requests.
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
# For testing with mocks
|
|
158
|
+
SQA::Stock.connection = mock_connection
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## SQA::DataFrame
|
|
27
164
|
|
|
28
|
-
### DataFrame Class
|
|
29
165
|
High-performance wrapper around Polars DataFrame.
|
|
30
166
|
|
|
167
|
+
### Constructor
|
|
168
|
+
|
|
169
|
+
#### `SQA::DataFrame.new(raw_data = nil, mapping: {}, transformers: {})`
|
|
170
|
+
|
|
171
|
+
Creates a new DataFrame instance.
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
df = SQA::DataFrame.new(data, mapping: { "Close" => "close_price" })
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Instance Attributes
|
|
178
|
+
|
|
179
|
+
| Attribute | Type | Description |
|
|
180
|
+
|-----------|------|-------------|
|
|
181
|
+
| `data` | Polars::DataFrame | Underlying Polars DataFrame |
|
|
182
|
+
|
|
183
|
+
### Instance Methods
|
|
184
|
+
|
|
185
|
+
#### `#columns` / `#keys`
|
|
186
|
+
|
|
187
|
+
Returns column names.
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
df.columns # => ["timestamp", "open_price", "high_price", ...]
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### `#[](column_name)`
|
|
194
|
+
|
|
195
|
+
Access a column (delegates to Polars).
|
|
196
|
+
|
|
31
197
|
```ruby
|
|
32
|
-
df = stock.df
|
|
33
198
|
prices = df["adj_close_price"].to_a
|
|
34
199
|
```
|
|
35
200
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
201
|
+
#### `#size` / `#nrows` / `#length`
|
|
202
|
+
|
|
203
|
+
Returns number of rows.
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
df.size # => 1258
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### `#ncols`
|
|
210
|
+
|
|
211
|
+
Returns number of columns.
|
|
212
|
+
|
|
213
|
+
#### `#to_csv(path)`
|
|
214
|
+
|
|
215
|
+
Writes DataFrame to CSV file.
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
df.to_csv("output.csv")
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### `#to_h`
|
|
222
|
+
|
|
223
|
+
Converts to Ruby Hash.
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
hash = df.to_h
|
|
227
|
+
# => { timestamp: [...], close_price: [...], ... }
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### `#append!(other_df)` / `#concat!(other_df)`
|
|
231
|
+
|
|
232
|
+
Appends another DataFrame in place.
|
|
233
|
+
|
|
234
|
+
#### `#concat_and_deduplicate!(other_df, sort_column: "timestamp")`
|
|
235
|
+
|
|
236
|
+
Appends, removes duplicates, and sorts. Enforces ascending order for TA-Lib compatibility.
|
|
237
|
+
|
|
238
|
+
#### `#rename_columns!(mapping)`
|
|
239
|
+
|
|
240
|
+
Renames columns according to mapping.
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
df.rename_columns!({ "open" => "open_price", "close" => "close_price" })
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### `#apply_transformers!(transformers)`
|
|
247
|
+
|
|
248
|
+
Applies transformer functions to columns.
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
df.apply_transformers!({ "volume" => ->(v) { v.to_i } })
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### `#fpl(column:, fpop:)`
|
|
255
|
+
|
|
256
|
+
Calculates Future Period Loss/Profit.
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
fpl_data = df.fpl(column: "adj_close_price", fpop: 10)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### `#fpl_analysis(column:, fpop:)`
|
|
263
|
+
|
|
264
|
+
FPL analysis with risk metrics.
|
|
265
|
+
|
|
266
|
+
### Class Methods
|
|
267
|
+
|
|
268
|
+
#### `SQA::DataFrame.load(source:, mapping: {}, transformers: {})`
|
|
269
|
+
|
|
270
|
+
Loads DataFrame from CSV file.
|
|
271
|
+
|
|
272
|
+
```ruby
|
|
273
|
+
df = SQA::DataFrame.load(source: "path/to/data.csv")
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### `SQA::DataFrame.from_aofh(aofh, mapping: {}, transformers: {})`
|
|
41
277
|
|
|
42
|
-
|
|
43
|
-
Base class for all trading strategies.
|
|
278
|
+
Creates DataFrame from array of hashes.
|
|
44
279
|
|
|
45
280
|
```ruby
|
|
46
|
-
|
|
281
|
+
data = [{ "date" => "2024-01-01", "price" => 100.0 }]
|
|
282
|
+
df = SQA::DataFrame.from_aofh(data)
|
|
47
283
|
```
|
|
48
284
|
|
|
49
|
-
|
|
50
|
-
- `.trade(vector)` - Generate trading signal
|
|
51
|
-
- `.trade_against(vector)` - Invert signal
|
|
285
|
+
#### `SQA::DataFrame.from_csv_file(source, mapping: {}, transformers: {})`
|
|
52
286
|
|
|
53
|
-
|
|
287
|
+
Creates DataFrame from CSV file.
|
|
288
|
+
|
|
289
|
+
#### `SQA::DataFrame.from_json_file(source, mapping: {}, transformers: {})`
|
|
290
|
+
|
|
291
|
+
Creates DataFrame from JSON file.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## SQA::Strategy
|
|
296
|
+
|
|
297
|
+
Framework for managing trading strategies.
|
|
298
|
+
|
|
299
|
+
### Constructor
|
|
300
|
+
|
|
301
|
+
#### `SQA::Strategy.new`
|
|
302
|
+
|
|
303
|
+
Creates a new Strategy manager with empty collection.
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
strategy = SQA::Strategy.new
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Instance Attributes
|
|
310
|
+
|
|
311
|
+
| Attribute | Type | Description |
|
|
312
|
+
|-----------|------|-------------|
|
|
313
|
+
| `strategies` | Array<Method> | Collection of strategy methods |
|
|
314
|
+
|
|
315
|
+
### Instance Methods
|
|
316
|
+
|
|
317
|
+
#### `#add(strategy)`
|
|
318
|
+
|
|
319
|
+
Adds a trading strategy.
|
|
320
|
+
|
|
321
|
+
```ruby
|
|
322
|
+
strategy.add(SQA::Strategy::RSI)
|
|
323
|
+
strategy.add(MyModule.method(:custom_trade))
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Parameters:**
|
|
327
|
+
- `strategy` (Class or Method) - Strategy to add
|
|
328
|
+
|
|
329
|
+
**Raises:** `BadParameterError` if not Class or Method
|
|
330
|
+
|
|
331
|
+
#### `#execute(vector)`
|
|
332
|
+
|
|
333
|
+
Executes all strategies with given data.
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
signals = strategy.execute(vector)
|
|
337
|
+
# => [:buy, :hold, :sell]
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Parameters:**
|
|
341
|
+
- `vector` (OpenStruct) - Data for strategy analysis
|
|
342
|
+
|
|
343
|
+
**Returns:** `Array<Symbol>` - Array of `:buy`, `:sell`, or `:hold`
|
|
344
|
+
|
|
345
|
+
#### `#auto_load(except: [:common], only: [])`
|
|
346
|
+
|
|
347
|
+
Auto-loads strategy files from strategy directory.
|
|
348
|
+
|
|
349
|
+
```ruby
|
|
350
|
+
strategy.auto_load(only: [:rsi, :macd])
|
|
351
|
+
strategy.auto_load(except: [:random])
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### `#available`
|
|
355
|
+
|
|
356
|
+
Lists all available strategy classes.
|
|
357
|
+
|
|
358
|
+
```ruby
|
|
359
|
+
strategy.available
|
|
360
|
+
# => [SQA::Strategy::RSI, SQA::Strategy::MACD, ...]
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Strategy Interface
|
|
364
|
+
|
|
365
|
+
All strategy classes must implement:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
class SQA::Strategy::MyStrategy
|
|
369
|
+
def self.trade(vector)
|
|
370
|
+
# Returns :buy, :sell, or :hold
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## SQA::Portfolio
|
|
54
378
|
|
|
55
|
-
### Portfolio Class
|
|
56
379
|
Track positions and calculate P&L.
|
|
57
380
|
|
|
381
|
+
### Constructor
|
|
382
|
+
|
|
383
|
+
#### `SQA::Portfolio.new(initial_cash:, commission: 0.0)`
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
portfolio = SQA::Portfolio.new(initial_cash: 10_000, commission: 1.0)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Instance Attributes
|
|
390
|
+
|
|
391
|
+
| Attribute | Type | Description |
|
|
392
|
+
|-----------|------|-------------|
|
|
393
|
+
| `cash` | Float | Available cash balance |
|
|
394
|
+
| `positions` | Hash | Ticker → share count |
|
|
395
|
+
| `trades` | Array | Trade history |
|
|
396
|
+
|
|
397
|
+
### Instance Methods
|
|
398
|
+
|
|
399
|
+
#### `#buy(ticker, shares:, price:)`
|
|
400
|
+
|
|
401
|
+
Purchases shares.
|
|
402
|
+
|
|
58
403
|
```ruby
|
|
59
|
-
portfolio = SQA::Portfolio.new(initial_cash: 10_000)
|
|
60
404
|
portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
61
405
|
```
|
|
62
406
|
|
|
63
|
-
|
|
407
|
+
#### `#sell(ticker, shares:, price:)`
|
|
408
|
+
|
|
409
|
+
Sells shares.
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
portfolio.sell('AAPL', shares: 5, price: 160.0)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### `#value(current_prices)`
|
|
416
|
+
|
|
417
|
+
Calculates total portfolio value.
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
420
|
+
total = portfolio.value({ 'AAPL' => 165.0 })
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Parameters:**
|
|
424
|
+
- `current_prices` (Hash) - Ticker → current price
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## SQA::Backtest
|
|
429
|
+
|
|
64
430
|
Simulate strategies on historical data.
|
|
65
431
|
|
|
432
|
+
### Constructor
|
|
433
|
+
|
|
434
|
+
#### `SQA::Backtest.new(stock:, strategy:, initial_cash: 10_000, commission: 0.0)`
|
|
435
|
+
|
|
66
436
|
```ruby
|
|
67
|
-
backtest = SQA::Backtest.new(
|
|
68
|
-
|
|
437
|
+
backtest = SQA::Backtest.new(
|
|
438
|
+
stock: stock,
|
|
439
|
+
strategy: SQA::Strategy::RSI,
|
|
440
|
+
initial_cash: 10_000,
|
|
441
|
+
commission: 1.0
|
|
442
|
+
)
|
|
69
443
|
```
|
|
70
444
|
|
|
71
|
-
###
|
|
72
|
-
|
|
445
|
+
### Instance Methods
|
|
446
|
+
|
|
447
|
+
#### `#run`
|
|
448
|
+
|
|
449
|
+
Executes the backtest simulation.
|
|
73
450
|
|
|
74
451
|
```ruby
|
|
75
|
-
|
|
76
|
-
stream.on_signal { |signal, data| puts "Signal: #{signal}" }
|
|
452
|
+
results = backtest.run
|
|
77
453
|
```
|
|
78
454
|
|
|
79
|
-
|
|
455
|
+
**Returns:** Results object with metrics:
|
|
456
|
+
|
|
457
|
+
| Metric | Description |
|
|
458
|
+
|--------|-------------|
|
|
459
|
+
| `total_return` | Total percentage return |
|
|
460
|
+
| `sharpe_ratio` | Risk-adjusted return |
|
|
461
|
+
| `sortino_ratio` | Downside risk-adjusted return |
|
|
462
|
+
| `max_drawdown` | Largest peak-to-trough decline |
|
|
463
|
+
| `win_rate` | Percentage of profitable trades |
|
|
464
|
+
| `num_trades` | Total number of trades |
|
|
465
|
+
| `final_value` | Ending portfolio value |
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## SQA::Config
|
|
470
|
+
|
|
471
|
+
Configuration management with Hashie::Dash.
|
|
472
|
+
|
|
473
|
+
### Properties
|
|
474
|
+
|
|
475
|
+
| Property | Type | Default | Description |
|
|
476
|
+
|----------|------|---------|-------------|
|
|
477
|
+
| `data_dir` | String | `~/sqa_data` | Data storage directory |
|
|
478
|
+
| `log_level` | Symbol | `:info` | Log level |
|
|
479
|
+
| `debug` | Boolean | `false` | Debug mode |
|
|
480
|
+
| `verbose` | Boolean | `false` | Verbose output |
|
|
481
|
+
| `lazy_update` | Boolean | `false` | Skip API updates |
|
|
482
|
+
| `plotting_library` | Symbol | `:gruff` | Plotting library |
|
|
483
|
+
|
|
484
|
+
### Instance Methods
|
|
485
|
+
|
|
486
|
+
#### `#debug?` / `#verbose?`
|
|
487
|
+
|
|
488
|
+
Check mode flags.
|
|
489
|
+
|
|
490
|
+
#### `#from_file`
|
|
491
|
+
|
|
492
|
+
Loads configuration from file (YAML, TOML, or JSON).
|
|
493
|
+
|
|
494
|
+
#### `#dump_file`
|
|
495
|
+
|
|
496
|
+
Saves configuration to file.
|
|
497
|
+
|
|
498
|
+
### Class Methods
|
|
499
|
+
|
|
500
|
+
#### `SQA::Config.reset`
|
|
501
|
+
|
|
502
|
+
Resets configuration to defaults.
|
|
503
|
+
|
|
504
|
+
#### `SQA::Config.initialized?`
|
|
80
505
|
|
|
81
|
-
|
|
506
|
+
Check if config has been initialized.
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## SQAI Indicators
|
|
511
|
+
|
|
512
|
+
All indicators from TA-Lib via the `sqa-tai` gem.
|
|
513
|
+
|
|
514
|
+
### Common Usage
|
|
82
515
|
|
|
83
516
|
```ruby
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
517
|
+
prices = stock.df["adj_close_price"].to_a
|
|
518
|
+
|
|
519
|
+
# Moving Averages
|
|
520
|
+
sma = SQAI.sma(prices, period: 20)
|
|
521
|
+
ema = SQAI.ema(prices, period: 12)
|
|
87
522
|
|
|
88
|
-
#
|
|
89
|
-
SQAI.rsi(prices, period: 14)
|
|
90
|
-
|
|
523
|
+
# Momentum
|
|
524
|
+
rsi = SQAI.rsi(prices, period: 14)
|
|
525
|
+
macd, signal, hist = SQAI.macd(prices,
|
|
526
|
+
fast_period: 12,
|
|
527
|
+
slow_period: 26,
|
|
528
|
+
signal_period: 9
|
|
529
|
+
)
|
|
91
530
|
|
|
92
531
|
# Volatility
|
|
93
|
-
SQAI.bbands(prices, period: 20
|
|
94
|
-
SQAI.atr(high, low, close, period: 14)
|
|
532
|
+
upper, middle, lower = SQAI.bbands(prices, period: 20)
|
|
533
|
+
atr = SQAI.atr(high, low, close, period: 14)
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### List All Indicators
|
|
537
|
+
|
|
538
|
+
```ruby
|
|
539
|
+
SQAI.methods.grep(/^[a-z]/).sort
|
|
540
|
+
# => [:acos, :ad, :add, :adosc, :adx, :adxr, ...]
|
|
95
541
|
```
|
|
96
542
|
|
|
97
|
-
See
|
|
543
|
+
See [Technical Indicators](../indicators/index.md) for complete reference.
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Error Classes
|
|
98
548
|
|
|
99
|
-
|
|
549
|
+
### SQA::DataFetchError
|
|
100
550
|
|
|
101
|
-
|
|
102
|
-
Manage SQA configuration.
|
|
551
|
+
Raised when unable to fetch data from API or file.
|
|
103
552
|
|
|
104
553
|
```ruby
|
|
105
|
-
|
|
554
|
+
begin
|
|
555
|
+
stock = SQA::Stock.new(ticker: 'INVALID')
|
|
556
|
+
rescue SQA::DataFetchError => e
|
|
557
|
+
puts e.message
|
|
558
|
+
puts e.original_error # Wrapped exception
|
|
559
|
+
end
|
|
106
560
|
```
|
|
107
561
|
|
|
108
|
-
|
|
109
|
-
- `data_dir` - Data storage location
|
|
110
|
-
- `log_level` - Logging verbosity
|
|
111
|
-
- `debug` - Debug mode flag
|
|
562
|
+
### SQA::ConfigurationError
|
|
112
563
|
|
|
113
|
-
|
|
564
|
+
Raised for invalid or missing configuration.
|
|
114
565
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
566
|
+
```ruby
|
|
567
|
+
begin
|
|
568
|
+
key = SQA.av_api_key
|
|
569
|
+
rescue SQA::ConfigurationError => e
|
|
570
|
+
puts "API key not set"
|
|
571
|
+
end
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### SQA::BadParameterError
|
|
575
|
+
|
|
576
|
+
Raised for invalid method parameters.
|
|
577
|
+
|
|
578
|
+
```ruby
|
|
579
|
+
begin
|
|
580
|
+
strategy.add("not a class")
|
|
581
|
+
rescue SQA::BadParameterError
|
|
582
|
+
puts "Invalid strategy type"
|
|
583
|
+
end
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### ApiError
|
|
587
|
+
|
|
588
|
+
Raised when external API returns error response.
|
|
589
|
+
|
|
590
|
+
### NotImplemented
|
|
591
|
+
|
|
592
|
+
Raised for unimplemented features.
|
|
118
593
|
|
|
119
594
|
---
|
|
120
595
|
|
|
121
|
-
|
|
596
|
+
## Introspection
|
|
597
|
+
|
|
598
|
+
Use Ruby introspection to explore the API:
|
|
122
599
|
|
|
123
600
|
```ruby
|
|
601
|
+
# List instance methods
|
|
124
602
|
SQA::Stock.instance_methods(false)
|
|
603
|
+
|
|
604
|
+
# List class methods
|
|
605
|
+
SQA::Stock.methods(false)
|
|
606
|
+
|
|
607
|
+
# Check method signature
|
|
608
|
+
SQA::Stock.instance_method(:initialize).parameters
|
|
609
|
+
|
|
610
|
+
# List available indicators
|
|
125
611
|
SQAI.methods.grep(/^[a-z]/).sort
|
|
126
612
|
```
|