sqa 0.0.37 → 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 +62 -0
- data/docs/api-reference/alphavantageapi.md +105 -105
- data/docs/api-reference/apierror.md +2 -2
- data/docs/api-reference/index.md +2 -2
- data/docs/api-reference/notimplemented.md +2 -2
- data/docs/api-reference/sqa.md +12 -12
- data/docs/api-reference/sqa_backtest.md +43 -9
- data/docs/api-reference/sqa_backtest_results.md +34 -34
- data/docs/api-reference/sqa_badparametererror.md +1 -1
- data/docs/api-reference/sqa_config.md +31 -31
- data/docs/api-reference/sqa_configurationerror.md +1 -1
- data/docs/api-reference/sqa_datafetcherror.md +3 -3
- data/docs/api-reference/sqa_dataframe.md +63 -36
- data/docs/api-reference/sqa_dataframe_alphavantage.md +2 -2
- data/docs/api-reference/sqa_dataframe_data.md +18 -18
- data/docs/api-reference/sqa_dataframe_yahoofinance.md +2 -2
- data/docs/api-reference/sqa_ensemble.md +21 -21
- data/docs/api-reference/sqa_fpop.md +8 -8
- data/docs/api-reference/sqa_geneticprogram.md +20 -20
- data/docs/api-reference/sqa_geneticprogram_individual.md +8 -8
- data/docs/api-reference/sqa_marketregime.md +10 -10
- data/docs/api-reference/sqa_multitimeframe.md +11 -11
- data/docs/api-reference/sqa_patternmatcher.md +9 -9
- data/docs/api-reference/sqa_pluginmanager.md +4 -4
- data/docs/api-reference/sqa_portfolio.md +84 -27
- data/docs/api-reference/sqa_portfolio_position.md +12 -12
- data/docs/api-reference/sqa_portfolio_trade.md +16 -16
- data/docs/api-reference/sqa_portfoliooptimizer.md +10 -10
- data/docs/api-reference/sqa_riskmanager.md +12 -12
- data/docs/api-reference/sqa_seasonalanalyzer.md +6 -6
- data/docs/api-reference/sqa_sectoranalyzer.md +9 -9
- data/docs/api-reference/sqa_stock.md +48 -36
- data/docs/api-reference/sqa_strategy.md +8 -8
- data/docs/api-reference/sqa_strategy_bollingerbands.md +2 -2
- data/docs/api-reference/sqa_strategy_common.md +3 -3
- data/docs/api-reference/sqa_strategy_consensus.md +12 -12
- data/docs/api-reference/sqa_strategy_ema.md +4 -4
- data/docs/api-reference/sqa_strategy_kbs.md +11 -11
- data/docs/api-reference/sqa_strategy_macd.md +2 -2
- data/docs/api-reference/sqa_strategy_mp.md +4 -4
- data/docs/api-reference/sqa_strategy_mr.md +4 -4
- data/docs/api-reference/sqa_strategy_random.md +4 -4
- data/docs/api-reference/sqa_strategy_rsi.md +4 -4
- data/docs/api-reference/sqa_strategy_sma.md +4 -4
- data/docs/api-reference/sqa_strategy_stochastic.md +2 -2
- data/docs/api-reference/sqa_strategy_volumebreakout.md +2 -2
- data/docs/api-reference/sqa_strategygenerator.md +19 -19
- data/docs/api-reference/sqa_strategygenerator_pattern.md +17 -17
- data/docs/api-reference/sqa_strategygenerator_patterncontext.md +21 -21
- data/docs/api-reference/sqa_strategygenerator_profitablepoint.md +27 -27
- data/docs/api-reference/sqa_stream.md +18 -18
- data/docs/api-reference/sqa_ticker.md +8 -8
- data/docs/api-reference/string.md +11 -11
- data/docs/file_formats.md +250 -0
- data/lib/sqa/backtest.rb +32 -0
- data/lib/sqa/data_frame.rb +25 -0
- data/lib/sqa/portfolio.rb +54 -0
- data/lib/sqa/stock.rb +11 -0
- data/lib/sqa/version.rb +1 -1
- data/mkdocs.yml +1 -0
- metadata +2 -2
- data/docs/IMPROVEMENT_PLAN.md +0 -531
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Desc: Monkey to the String class.
|
|
6
6
|
|
|
7
7
|
!!! abstract "Source Information"
|
|
8
|
-
**Defined in:** `lib/patches/string.rb:10`
|
|
8
|
+
**Defined in:** [`lib/patches/string.rb:10`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L10)
|
|
9
9
|
|
|
10
10
|
**Inherits from:** `Object`
|
|
11
11
|
|
|
@@ -19,7 +19,7 @@ Convert CamelCase to camel_case
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
??? info "Source Location"
|
|
22
|
-
`lib/patches/string.rb:14`
|
|
22
|
+
[`lib/patches/string.rb:14`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L14)
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -31,7 +31,7 @@ Convert CamelCase to camel_case
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
??? info "Source Location"
|
|
34
|
-
`lib/patches/string.rb:21`
|
|
34
|
+
[`lib/patches/string.rb:21`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L21)
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
@@ -43,7 +43,7 @@ Convert CamelCase to camel_case
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
??? info "Source Location"
|
|
46
|
-
`lib/patches/string.rb:22`
|
|
46
|
+
[`lib/patches/string.rb:22`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L22)
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
@@ -55,7 +55,7 @@ Convert CamelCase to camel_case
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
??? info "Source Location"
|
|
58
|
-
`lib/patches/string.rb:23`
|
|
58
|
+
[`lib/patches/string.rb:23`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L23)
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
@@ -67,7 +67,7 @@ Convert CamelCase to camel_case
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
??? info "Source Location"
|
|
70
|
-
`lib/patches/string.rb:24`
|
|
70
|
+
[`lib/patches/string.rb:24`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L24)
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
|
|
@@ -79,7 +79,7 @@ Convert camel_case to CamelCase
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
??? info "Source Location"
|
|
82
|
-
`lib/patches/string.rb:29`
|
|
82
|
+
[`lib/patches/string.rb:29`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L29)
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
@@ -91,7 +91,7 @@ Convert camel_case to CamelCase
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
??? info "Source Location"
|
|
94
|
-
`lib/patches/string.rb:34`
|
|
94
|
+
[`lib/patches/string.rb:34`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L34)
|
|
95
95
|
|
|
96
96
|
---
|
|
97
97
|
|
|
@@ -103,7 +103,7 @@ Convert camel_case to CamelCase
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
??? info "Source Location"
|
|
106
|
-
`lib/patches/string.rb:35`
|
|
106
|
+
[`lib/patches/string.rb:35`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L35)
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
|
@@ -115,7 +115,7 @@ Convert "CamelCase" into CamelCase
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
??? info "Source Location"
|
|
118
|
-
`lib/patches/string.rb:40`
|
|
118
|
+
[`lib/patches/string.rb:40`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L40)
|
|
119
119
|
|
|
120
120
|
---
|
|
121
121
|
|
|
@@ -127,7 +127,7 @@ Convert "CamelCase" into CamelCase
|
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
??? info "Source Location"
|
|
130
|
-
`lib/patches/string.rb:55`
|
|
130
|
+
[`lib/patches/string.rb:55`](https://github.com/madbomber/sqa/blob/main/lib/patches/string.rb#L55)
|
|
131
131
|
|
|
132
132
|
---
|
|
133
133
|
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# File Formats
|
|
2
|
+
|
|
3
|
+
This document describes the CSV file formats used by SQA for data import and export.
|
|
4
|
+
|
|
5
|
+
## Portfolio CSV Formats
|
|
6
|
+
|
|
7
|
+
The `SQA::Portfolio` class supports two CSV file formats for different purposes: positions (holdings) and trade history.
|
|
8
|
+
|
|
9
|
+
### Portfolio Positions CSV
|
|
10
|
+
|
|
11
|
+
This format is used to save and load current portfolio holdings.
|
|
12
|
+
|
|
13
|
+
**Methods:**
|
|
14
|
+
- `portfolio.save_to_csv(filename)` - Save positions
|
|
15
|
+
- `SQA::Portfolio.load_from_csv(filename)` - Load positions
|
|
16
|
+
|
|
17
|
+
**Schema:**
|
|
18
|
+
|
|
19
|
+
| Column | Type | Description |
|
|
20
|
+
|--------|------|-------------|
|
|
21
|
+
| `ticker` | String | Stock ticker symbol (e.g., 'AAPL', 'MSFT') |
|
|
22
|
+
| `shares` | Integer | Number of shares currently held |
|
|
23
|
+
| `avg_cost` | Float | Average cost per share (cost basis) |
|
|
24
|
+
| `total_cost` | Float | Total cost basis for the entire position |
|
|
25
|
+
|
|
26
|
+
**Example File (`portfolio.csv`):**
|
|
27
|
+
|
|
28
|
+
```csv
|
|
29
|
+
ticker,shares,avg_cost,total_cost
|
|
30
|
+
AAPL,100,150.0,15000.0
|
|
31
|
+
MSFT,50,300.0,15000.0
|
|
32
|
+
GOOG,25,120.0,3000.0
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Usage:**
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# Save current positions to CSV
|
|
39
|
+
portfolio = SQA::Portfolio.new(initial_cash: 100_000)
|
|
40
|
+
portfolio.buy('AAPL', shares: 100, price: 150.0)
|
|
41
|
+
portfolio.buy('MSFT', shares: 50, price: 300.0)
|
|
42
|
+
portfolio.save_to_csv('my_portfolio.csv')
|
|
43
|
+
|
|
44
|
+
# Load positions from CSV
|
|
45
|
+
loaded_portfolio = SQA::Portfolio.load_from_csv('my_portfolio.csv')
|
|
46
|
+
loaded_portfolio.position('AAPL').shares # => 100
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Notes:**
|
|
50
|
+
- The CSV includes only current positions (open holdings)
|
|
51
|
+
- Cash balance is NOT saved in the CSV (set via `initial_cash` parameter when loading)
|
|
52
|
+
- Use this format for portfolio snapshots and position tracking
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### Trade History CSV
|
|
57
|
+
|
|
58
|
+
This format is used to export the complete log of all buy and sell transactions.
|
|
59
|
+
|
|
60
|
+
**Methods:**
|
|
61
|
+
- `portfolio.save_trades_to_csv(filename)` - Export trade history
|
|
62
|
+
|
|
63
|
+
**Schema:**
|
|
64
|
+
|
|
65
|
+
| Column | Type | Description |
|
|
66
|
+
|--------|------|-------------|
|
|
67
|
+
| `date` | Date | Trade execution date (YYYY-MM-DD format) |
|
|
68
|
+
| `ticker` | String | Stock ticker symbol |
|
|
69
|
+
| `action` | Symbol | Trade type: `buy` or `sell` |
|
|
70
|
+
| `shares` | Integer | Number of shares traded |
|
|
71
|
+
| `price` | Float | Price per share at execution |
|
|
72
|
+
| `total` | Float | Total transaction value (shares × price) |
|
|
73
|
+
| `commission` | Float | Commission paid for the trade |
|
|
74
|
+
|
|
75
|
+
**Example File (`trades.csv`):**
|
|
76
|
+
|
|
77
|
+
```csv
|
|
78
|
+
date,ticker,action,shares,price,total,commission
|
|
79
|
+
2024-01-15,AAPL,buy,100,150.0,15000.0,1.0
|
|
80
|
+
2024-01-20,MSFT,buy,50,300.0,15000.0,1.0
|
|
81
|
+
2024-02-05,AAPL,sell,50,160.0,8000.0,1.0
|
|
82
|
+
2024-02-15,GOOG,buy,25,120.0,3000.0,1.0
|
|
83
|
+
2024-03-01,MSFT,sell,25,310.0,7750.0,1.0
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Usage:**
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# Execute some trades
|
|
90
|
+
portfolio = SQA::Portfolio.new(initial_cash: 100_000, commission: 1.0)
|
|
91
|
+
portfolio.buy('AAPL', shares: 100, price: 150.0, date: Date.parse('2024-01-15'))
|
|
92
|
+
portfolio.buy('MSFT', shares: 50, price: 300.0, date: Date.parse('2024-01-20'))
|
|
93
|
+
portfolio.sell('AAPL', shares: 50, price: 160.0, date: Date.parse('2024-02-05'))
|
|
94
|
+
|
|
95
|
+
# Export complete trade history
|
|
96
|
+
portfolio.save_trades_to_csv('trade_history.csv')
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Notes:**
|
|
100
|
+
- This format is write-only (no corresponding `load_trades_from_csv()` method)
|
|
101
|
+
- Use for audit trails, tax reporting, and performance analysis
|
|
102
|
+
- Each row represents a single executed trade
|
|
103
|
+
- The `total` column does NOT include commission (commission is tracked separately)
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Stock Data CSV Format
|
|
108
|
+
|
|
109
|
+
Stock price and volume data is stored in CSV format with the following schema:
|
|
110
|
+
|
|
111
|
+
**Schema:**
|
|
112
|
+
|
|
113
|
+
| Column | Type | Description |
|
|
114
|
+
|--------|------|-------------|
|
|
115
|
+
| `timestamp` | String | Date in YYYY-MM-DD format |
|
|
116
|
+
| `open_price` | Float | Opening price |
|
|
117
|
+
| `high_price` | Float | Highest price during the period |
|
|
118
|
+
| `low_price` | Float | Lowest price during the period |
|
|
119
|
+
| `close_price` | Float | Closing price |
|
|
120
|
+
| `adj_close_price` | Float | Adjusted closing price (accounts for splits/dividends) |
|
|
121
|
+
| `volume` | Integer | Trading volume |
|
|
122
|
+
|
|
123
|
+
**Example File (`aapl.csv`):**
|
|
124
|
+
|
|
125
|
+
```csv
|
|
126
|
+
timestamp,open_price,high_price,low_price,close_price,adj_close_price,volume
|
|
127
|
+
2023-01-03,130.28,130.90,124.17,125.07,124.38,112117500
|
|
128
|
+
2023-01-04,126.89,128.66,125.08,126.36,125.66,89113600
|
|
129
|
+
2023-01-05,127.13,127.77,124.76,125.02,124.33,80962700
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Location:**
|
|
133
|
+
- Stock CSV files are stored in `~/sqa_data/` by default (configurable via `SQA::Config`)
|
|
134
|
+
- File naming convention: `{ticker}.csv` (e.g., `aapl.csv`, `msft.csv`)
|
|
135
|
+
|
|
136
|
+
**Data Ordering:**
|
|
137
|
+
- **CRITICAL:** Data MUST be in ascending chronological order (oldest first, newest last)
|
|
138
|
+
- This ordering is required for TA-Lib compatibility
|
|
139
|
+
- Index [0] = oldest data point, Index [last] = newest data point
|
|
140
|
+
|
|
141
|
+
**Usage:**
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Stock data is automatically loaded/saved
|
|
145
|
+
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
146
|
+
stock.df.to_csv('aapl_export.csv') # Export to custom location
|
|
147
|
+
|
|
148
|
+
# Load from custom CSV
|
|
149
|
+
df = SQA::DataFrame.load(source: 'path/to/custom.csv')
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Notes:**
|
|
153
|
+
- Data is automatically fetched from Alpha Vantage or Yahoo Finance on first load
|
|
154
|
+
- Updates are appended and deduplicated using `concat_and_deduplicate!`
|
|
155
|
+
- Use `adj_close_price` for calculations that need to account for corporate actions
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Stock Metadata JSON Format
|
|
160
|
+
|
|
161
|
+
Stock metadata (company information) is stored in JSON format alongside CSV files.
|
|
162
|
+
|
|
163
|
+
**Location:** `~/sqa_data/{ticker}.json`
|
|
164
|
+
|
|
165
|
+
**Example File (`aapl.json`):**
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"ticker": "aapl",
|
|
170
|
+
"name": "Apple Inc.",
|
|
171
|
+
"exchange": "NASDAQ",
|
|
172
|
+
"source": "alpha_vantage",
|
|
173
|
+
"indicators": {},
|
|
174
|
+
"overview": {
|
|
175
|
+
"symbol": "AAPL",
|
|
176
|
+
"asset_type": "Common Stock",
|
|
177
|
+
"name": "Apple Inc.",
|
|
178
|
+
"exchange": "NASDAQ",
|
|
179
|
+
"currency": "USD",
|
|
180
|
+
"country": "USA",
|
|
181
|
+
"sector": "TECHNOLOGY",
|
|
182
|
+
"industry": "ELECTRONIC COMPUTERS",
|
|
183
|
+
"market_capitalization": 2500000000000,
|
|
184
|
+
"pe_ratio": 28.5,
|
|
185
|
+
"eps": 6.05,
|
|
186
|
+
"dividend_per_share": 0.92,
|
|
187
|
+
"dividend_yield": 0.0055
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Usage:**
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
196
|
+
stock.data.overview['market_capitalization'] # => 2500000000000
|
|
197
|
+
stock.data.overview['pe_ratio'] # => 28.5
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Configuration File Format
|
|
203
|
+
|
|
204
|
+
SQA supports YAML and TOML configuration files.
|
|
205
|
+
|
|
206
|
+
**Location:** `~/.sqa.yml` or `~/.sqa.toml`
|
|
207
|
+
|
|
208
|
+
**Example YAML (`~/.sqa.yml`):**
|
|
209
|
+
|
|
210
|
+
```yaml
|
|
211
|
+
data_dir: ~/sqa_data
|
|
212
|
+
lazy_update: false
|
|
213
|
+
log_level: info
|
|
214
|
+
plotting_library: gnuplot
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Example TOML (`~/.sqa.toml`):**
|
|
218
|
+
|
|
219
|
+
```toml
|
|
220
|
+
data_dir = "~/sqa_data"
|
|
221
|
+
lazy_update = false
|
|
222
|
+
log_level = "info"
|
|
223
|
+
plotting_library = "gnuplot"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Available Configuration Options:**
|
|
227
|
+
|
|
228
|
+
| Option | Type | Default | Description |
|
|
229
|
+
|--------|------|---------|-------------|
|
|
230
|
+
| `data_dir` | String | `~/sqa_data` | Directory for storing stock data files |
|
|
231
|
+
| `lazy_update` | Boolean | `false` | If true, skip automatic data updates |
|
|
232
|
+
| `log_level` | Symbol | `:info` | Logging level (`:debug`, `:info`, `:warn`, `:error`) |
|
|
233
|
+
| `plotting_library` | Symbol | `:gnuplot` | Plotting library to use |
|
|
234
|
+
|
|
235
|
+
**Usage:**
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
# Load custom configuration
|
|
239
|
+
config = SQA::Config.new(data_dir: '/path/to/data', lazy_update: true)
|
|
240
|
+
SQA.init(config)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## See Also
|
|
246
|
+
|
|
247
|
+
- [API Reference - Portfolio](api-reference/sqa_portfolio.md)
|
|
248
|
+
- [API Reference - Stock](api-reference/sqa_stock.md)
|
|
249
|
+
- [API Reference - DataFrame](api-reference/sqa_dataframe.md)
|
|
250
|
+
- [Data Frame Documentation](data_frame.md)
|
data/lib/sqa/backtest.rb
CHANGED
|
@@ -99,6 +99,38 @@ class SQA::Backtest
|
|
|
99
99
|
|
|
100
100
|
# Run the backtest
|
|
101
101
|
# @return [Results] Backtest results
|
|
102
|
+
#
|
|
103
|
+
# @example Run backtest with RSI strategy
|
|
104
|
+
# stock = SQA::Stock.new(ticker: 'AAPL')
|
|
105
|
+
# backtest = SQA::Backtest.new(
|
|
106
|
+
# stock: stock,
|
|
107
|
+
# strategy: SQA::Strategy::RSI,
|
|
108
|
+
# initial_capital: 10_000,
|
|
109
|
+
# commission: 1.0
|
|
110
|
+
# )
|
|
111
|
+
# results = backtest.run
|
|
112
|
+
# puts results.summary
|
|
113
|
+
# # => Total Return: 15.5%
|
|
114
|
+
# # Sharpe Ratio: 1.2
|
|
115
|
+
# # Max Drawdown: -8.3%
|
|
116
|
+
# # Win Rate: 65%
|
|
117
|
+
#
|
|
118
|
+
# @example Backtest with custom date range
|
|
119
|
+
# backtest = SQA::Backtest.new(
|
|
120
|
+
# stock: stock,
|
|
121
|
+
# strategy: SQA::Strategy::MACD,
|
|
122
|
+
# start_date: '2023-01-01',
|
|
123
|
+
# end_date: '2023-12-31'
|
|
124
|
+
# )
|
|
125
|
+
# results = backtest.run
|
|
126
|
+
# results.total_return # => 0.155 (15.5%)
|
|
127
|
+
#
|
|
128
|
+
# @example Access equity curve for plotting
|
|
129
|
+
# results = backtest.run
|
|
130
|
+
# backtest.equity_curve.each do |point|
|
|
131
|
+
# puts "#{point[:date]}: $#{point[:value]}"
|
|
132
|
+
# end
|
|
133
|
+
#
|
|
102
134
|
def run
|
|
103
135
|
# Get data for the backtest period
|
|
104
136
|
df = @stock.df.data
|
data/lib/sqa/data_frame.rb
CHANGED
|
@@ -132,6 +132,23 @@ class SQA::DataFrame
|
|
|
132
132
|
#
|
|
133
133
|
# NOTE: TA-Lib requires data in ascending (oldest-first) order. Using descending: true
|
|
134
134
|
# will produce a warning and force ascending order to prevent silent calculation errors.
|
|
135
|
+
#
|
|
136
|
+
# @example Merge new data with deduplication
|
|
137
|
+
# stock = SQA::Stock.new(ticker: 'AAPL')
|
|
138
|
+
# df = stock.df
|
|
139
|
+
# df.size # => 252
|
|
140
|
+
#
|
|
141
|
+
# # Fetch recent data (may have overlapping dates)
|
|
142
|
+
# new_df = SQA::DataFrame::AlphaVantage.recent('AAPL', from_date: Date.today - 7)
|
|
143
|
+
# df.concat_and_deduplicate!(new_df)
|
|
144
|
+
# # Duplicates removed, data sorted ascending (oldest first)
|
|
145
|
+
# df.size # => 255 (only 3 new unique dates added)
|
|
146
|
+
#
|
|
147
|
+
# @example Maintains TA-Lib compatibility
|
|
148
|
+
# df.concat_and_deduplicate!(new_df) # Sorted ascending automatically
|
|
149
|
+
# prices = df["adj_close_price"].to_a
|
|
150
|
+
# rsi = SQAI.rsi(prices, period: 14) # Works correctly with ascending data
|
|
151
|
+
#
|
|
135
152
|
def concat_and_deduplicate!(other_df, sort_column: "timestamp", descending: false)
|
|
136
153
|
# Enforce ascending order for TA-Lib compatibility
|
|
137
154
|
if descending
|
|
@@ -184,6 +201,14 @@ class SQA::DataFrame
|
|
|
184
201
|
#
|
|
185
202
|
# @param path_to_file [String, Pathname] Path to output CSV file
|
|
186
203
|
# @return [void]
|
|
204
|
+
#
|
|
205
|
+
# @example Save stock data to CSV
|
|
206
|
+
# stock = SQA::Stock.new(ticker: 'AAPL')
|
|
207
|
+
# stock.df.to_csv('aapl_prices.csv')
|
|
208
|
+
#
|
|
209
|
+
# @example Export with custom path
|
|
210
|
+
# df.to_csv(Pathname.new('data/exports/prices.csv'))
|
|
211
|
+
#
|
|
187
212
|
def to_csv(path_to_file)
|
|
188
213
|
@data.write_csv(path_to_file)
|
|
189
214
|
end
|
data/lib/sqa/portfolio.rb
CHANGED
|
@@ -52,6 +52,19 @@ class SQA::Portfolio
|
|
|
52
52
|
# @param price [Float] Price per share
|
|
53
53
|
# @param date [Date] Date of trade
|
|
54
54
|
# @return [Trade] The executed trade
|
|
55
|
+
#
|
|
56
|
+
# @example Buy 10 shares of AAPL
|
|
57
|
+
# portfolio = SQA::Portfolio.new(initial_cash: 10_000, commission: 1.0)
|
|
58
|
+
# trade = portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
59
|
+
# trade.action # => :buy
|
|
60
|
+
# trade.total # => 1500.0
|
|
61
|
+
# portfolio.cash # => 8499.0 (10_000 - 1500 - 1.0 commission)
|
|
62
|
+
#
|
|
63
|
+
# @example Buy multiple stocks
|
|
64
|
+
# portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
65
|
+
# portfolio.buy('MSFT', shares: 5, price: 300.0)
|
|
66
|
+
# portfolio.positions.size # => 2
|
|
67
|
+
#
|
|
55
68
|
def buy(ticker, shares:, price:, date: Date.today)
|
|
56
69
|
raise BadParameterError, "Shares must be positive" if shares <= 0
|
|
57
70
|
raise BadParameterError, "Price must be positive" if price <= 0
|
|
@@ -95,6 +108,19 @@ class SQA::Portfolio
|
|
|
95
108
|
# @param price [Float] Price per share
|
|
96
109
|
# @param date [Date] Date of trade
|
|
97
110
|
# @return [Trade] The executed trade
|
|
111
|
+
#
|
|
112
|
+
# @example Sell entire position for profit
|
|
113
|
+
# portfolio = SQA::Portfolio.new(initial_cash: 10_000, commission: 1.0)
|
|
114
|
+
# portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
115
|
+
# trade = portfolio.sell('AAPL', shares: 10, price: 160.0)
|
|
116
|
+
# trade.total # => 1600.0
|
|
117
|
+
# portfolio.cash # => 8498.0 + 1599.0 = 10097.0 (after commissions)
|
|
118
|
+
#
|
|
119
|
+
# @example Partial sale
|
|
120
|
+
# portfolio.buy('AAPL', shares: 100, price: 150.0)
|
|
121
|
+
# portfolio.sell('AAPL', shares: 50, price: 160.0) # Sell half
|
|
122
|
+
# portfolio.position('AAPL').shares # => 50
|
|
123
|
+
#
|
|
98
124
|
def sell(ticker, shares:, price:, date: Date.today)
|
|
99
125
|
raise BadParameterError, "Shares must be positive" if shares <= 0
|
|
100
126
|
raise BadParameterError, "Price must be positive" if price <= 0
|
|
@@ -145,6 +171,18 @@ class SQA::Portfolio
|
|
|
145
171
|
# Calculate total portfolio value
|
|
146
172
|
# @param current_prices [Hash] Hash of ticker => current_price
|
|
147
173
|
# @return [Float] Total portfolio value (cash + positions)
|
|
174
|
+
#
|
|
175
|
+
# @example Calculate portfolio value with current prices
|
|
176
|
+
# portfolio = SQA::Portfolio.new(initial_cash: 10_000)
|
|
177
|
+
# portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
178
|
+
# portfolio.buy('MSFT', shares: 5, price: 300.0)
|
|
179
|
+
#
|
|
180
|
+
# current_prices = { 'AAPL' => 160.0, 'MSFT' => 310.0 }
|
|
181
|
+
# portfolio.value(current_prices) # => 10_000 - 1500 - 1500 + 1600 + 1550 = 10_150
|
|
182
|
+
#
|
|
183
|
+
# @example Without current prices (uses avg_cost)
|
|
184
|
+
# portfolio.value # Uses purchase prices if no current prices provided
|
|
185
|
+
#
|
|
148
186
|
def value(current_prices = {})
|
|
149
187
|
positions_value = @positions.sum do |ticker, pos|
|
|
150
188
|
current_price = current_prices[ticker] || pos.avg_cost
|
|
@@ -186,6 +224,22 @@ class SQA::Portfolio
|
|
|
186
224
|
# Get summary statistics
|
|
187
225
|
# @param current_prices [Hash] Hash of ticker => current_price
|
|
188
226
|
# @return [Hash] Summary statistics
|
|
227
|
+
#
|
|
228
|
+
# @example Get portfolio performance summary
|
|
229
|
+
# portfolio = SQA::Portfolio.new(initial_cash: 10_000, commission: 1.0)
|
|
230
|
+
# portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
231
|
+
# portfolio.sell('AAPL', shares: 5, price: 160.0)
|
|
232
|
+
#
|
|
233
|
+
# summary = portfolio.summary({ 'AAPL' => 165.0 })
|
|
234
|
+
# summary[:initial_cash] # => 10_000.0
|
|
235
|
+
# summary[:current_cash] # => 8798.0
|
|
236
|
+
# summary[:positions_count] # => 1
|
|
237
|
+
# summary[:total_value] # => 9623.0
|
|
238
|
+
# summary[:profit_loss_percent] # => -3.77%
|
|
239
|
+
# summary[:total_trades] # => 2
|
|
240
|
+
# summary[:buy_trades] # => 1
|
|
241
|
+
# summary[:sell_trades] # => 1
|
|
242
|
+
#
|
|
189
243
|
def summary(current_prices = {})
|
|
190
244
|
{
|
|
191
245
|
initial_cash: @initial_cash,
|
data/lib/sqa/stock.rb
CHANGED
|
@@ -131,6 +131,17 @@ class SQA::Stock
|
|
|
131
131
|
# Silently handles errors since overview data is optional.
|
|
132
132
|
#
|
|
133
133
|
# @return [void]
|
|
134
|
+
#
|
|
135
|
+
# @example Update stock metadata from API
|
|
136
|
+
# stock = SQA::Stock.new(ticker: 'AAPL')
|
|
137
|
+
# stock.update # Fetches latest company overview data
|
|
138
|
+
# stock.data.overview['market_capitalization'] # => 2500000000000
|
|
139
|
+
# stock.data.overview['pe_ratio'] # => 28.5
|
|
140
|
+
#
|
|
141
|
+
# @example Update is safe if API fails
|
|
142
|
+
# stock.update # No error raised if API is unavailable
|
|
143
|
+
# # Warning logged but stock remains usable with cached data
|
|
144
|
+
#
|
|
134
145
|
def update
|
|
135
146
|
begin
|
|
136
147
|
merge_overview
|
data/lib/sqa/version.rb
CHANGED
data/mkdocs.yml
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sqa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.38
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -426,7 +426,6 @@ files:
|
|
|
426
426
|
- data/talk_talk.json
|
|
427
427
|
- develop_summary.md
|
|
428
428
|
- docs/.gitignore
|
|
429
|
-
- docs/IMPROVEMENT_PLAN.md
|
|
430
429
|
- docs/advanced/backtesting.md
|
|
431
430
|
- docs/advanced/ensemble.md
|
|
432
431
|
- docs/advanced/fpop.md
|
|
@@ -507,6 +506,7 @@ files:
|
|
|
507
506
|
- docs/data-sources/index.md
|
|
508
507
|
- docs/data_frame.md
|
|
509
508
|
- docs/factors_that_impact_price.md
|
|
509
|
+
- docs/file_formats.md
|
|
510
510
|
- docs/finviz.md
|
|
511
511
|
- docs/fx_pro_bit.md
|
|
512
512
|
- docs/genetic_programming.md
|