sqa 0.0.24 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.goose/memory/development.txt +3 -0
- data/.semver +6 -0
- data/ARCHITECTURE.md +648 -0
- data/CHANGELOG.md +82 -0
- data/CLAUDE.md +653 -0
- data/COMMITS.md +196 -0
- data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
- data/NEXT-STEPS.md +154 -0
- data/README.md +812 -262
- 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 +1115 -0
- data/docs/api/index.md +126 -0
- data/docs/assets/css/custom.css +88 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/concepts/index.md +68 -0
- data/docs/contributing/index.md +60 -0
- data/docs/data-sources/index.md +66 -0
- data/docs/data_frame.md +317 -97
- data/docs/factors_that_impact_price.md +26 -0
- data/docs/finviz.md +11 -0
- data/docs/fx_pro_bit.md +25 -0
- data/docs/genetic_programming.md +104 -0
- data/docs/getting-started/index.md +123 -0
- data/docs/getting-started/installation.md +229 -0
- data/docs/getting-started/quick-start.md +244 -0
- data/docs/i_gotta_an_idea.md +22 -0
- data/docs/index.md +163 -0
- data/docs/indicators/index.md +97 -0
- data/docs/indicators.md +110 -24
- data/docs/options.md +8 -0
- data/docs/strategies/bollinger-bands.md +146 -0
- data/docs/strategies/consensus.md +64 -0
- data/docs/strategies/custom.md +310 -0
- data/docs/strategies/ema.md +53 -0
- data/docs/strategies/index.md +92 -0
- data/docs/strategies/kbs.md +164 -0
- data/docs/strategies/macd.md +96 -0
- data/docs/strategies/market-profile.md +54 -0
- data/docs/strategies/mean-reversion.md +58 -0
- data/docs/strategies/rsi.md +95 -0
- data/docs/strategies/sma.md +55 -0
- data/docs/strategies/stochastic.md +63 -0
- data/docs/strategies/volume-breakout.md +54 -0
- data/docs/tags.md +7 -0
- data/docs/true_strength_index.md +46 -0
- data/docs/weighted_moving_average.md +48 -0
- data/examples/README.md +354 -0
- data/examples/advanced_features_example.rb +350 -0
- data/examples/fpop_analysis_example.rb +191 -0
- data/examples/genetic_programming_example.rb +148 -0
- data/examples/kbs_strategy_example.rb +208 -0
- data/examples/pattern_context_example.rb +300 -0
- data/examples/rails_app/Gemfile +34 -0
- data/examples/rails_app/README.md +416 -0
- data/examples/rails_app/app/assets/javascripts/application.js +107 -0
- data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
- data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
- data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
- data/examples/rails_app/app/controllers/application_controller.rb +22 -0
- data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
- data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
- data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
- data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
- data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
- data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
- data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
- data/examples/rails_app/app/views/errors/show.html.erb +17 -0
- data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
- data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
- data/examples/rails_app/bin/rails +6 -0
- data/examples/rails_app/config/application.rb +45 -0
- data/examples/rails_app/config/boot.rb +5 -0
- data/examples/rails_app/config/database.yml +18 -0
- data/examples/rails_app/config/environment.rb +11 -0
- data/examples/rails_app/config/routes.rb +26 -0
- data/examples/rails_app/config.ru +8 -0
- data/examples/realtime_stream_example.rb +274 -0
- data/examples/sinatra_app/Gemfile +22 -0
- data/examples/sinatra_app/QUICKSTART.md +159 -0
- data/examples/sinatra_app/README.md +461 -0
- data/examples/sinatra_app/app.rb +344 -0
- data/examples/sinatra_app/config.ru +5 -0
- data/examples/sinatra_app/public/css/style.css +659 -0
- data/examples/sinatra_app/public/js/app.js +107 -0
- data/examples/sinatra_app/views/analyze.erb +306 -0
- data/examples/sinatra_app/views/backtest.erb +325 -0
- data/examples/sinatra_app/views/dashboard.erb +419 -0
- data/examples/sinatra_app/views/error.erb +58 -0
- data/examples/sinatra_app/views/index.erb +118 -0
- data/examples/sinatra_app/views/layout.erb +61 -0
- data/examples/sinatra_app/views/portfolio.erb +43 -0
- data/examples/strategy_generator_example.rb +346 -0
- data/hsa_portfolio.csv +11 -0
- data/justfile +0 -0
- data/lib/api/alpha_vantage_api.rb +462 -0
- data/lib/sqa/backtest.rb +329 -0
- data/lib/sqa/data_frame/alpha_vantage.rb +43 -65
- 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 +148 -243
- data/lib/sqa/ensemble.rb +359 -0
- data/lib/sqa/fpop.rb +199 -0
- data/lib/sqa/gp.rb +259 -0
- data/lib/sqa/indicator.rb +5 -8
- data/lib/sqa/init.rb +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 +67 -125
- 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/version.rb +1 -7
- data/lib/sqa.rb +23 -16
- data/main.just +81 -0
- data/mkdocs.yml +288 -0
- data/trace.log +0 -0
- metadata +261 -51
- data/bin/sqa +0 -6
- 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
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# FPOP - Future Period of Performance
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Calculate future returns, risk metrics, and direction classification to evaluate trading opportunities.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
require 'sqa'
|
|
11
|
+
|
|
12
|
+
stock = SQA::Stock.new(ticker: 'AAPL')
|
|
13
|
+
prices = stock.df["adj_close_price"].to_a
|
|
14
|
+
|
|
15
|
+
# Calculate FPL (Future Period Loss/Profit)
|
|
16
|
+
fpl_data = SQA::FPOP.fpl(prices, fpop: 10)
|
|
17
|
+
# => [[min_delta, max_delta], ...]
|
|
18
|
+
|
|
19
|
+
# Comprehensive analysis
|
|
20
|
+
analysis = SQA::FPOP.fpl_analysis(prices, fpop: 10)
|
|
21
|
+
puts "Risk: #{analysis[:risk]}%"
|
|
22
|
+
puts "Direction: #{analysis[:direction]}" # :UP, :DOWN, :FLAT
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Methods
|
|
26
|
+
|
|
27
|
+
### Basic FPL Calculation
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
prices = [100, 102, 105, 103, 107, 110]
|
|
31
|
+
fpl = SQA::FPOP.fpl(prices, fpop: 3)
|
|
32
|
+
|
|
33
|
+
# For each point, calculates min/max change over next 3 periods
|
|
34
|
+
# fpl[0] = [min(102,105,103) - 100, max(102,105,103) - 100]
|
|
35
|
+
# = [100-100, 105-100] = [0, 5]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Comprehensive Analysis
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
analysis = SQA::FPOP.fpl_analysis(prices, fpop: 10)
|
|
42
|
+
|
|
43
|
+
# Returns hash with:
|
|
44
|
+
# {
|
|
45
|
+
# fpl: [[min_delta, max_delta], ...],
|
|
46
|
+
# risk: 15.5, # Max downside %
|
|
47
|
+
# reward: 25.2, # Max upside %
|
|
48
|
+
# direction: :UP, # Overall trend
|
|
49
|
+
# magnitude: 10.3, # Average move size
|
|
50
|
+
# quality_score: 0.75 # Risk/reward ratio
|
|
51
|
+
# }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Quality Filtering
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
filtered = SQA::FPOP.filter_by_quality(
|
|
58
|
+
analysis,
|
|
59
|
+
min_magnitude: 5.0, # Minimum 5% move
|
|
60
|
+
max_risk: 25.0, # Maximum 25% downside
|
|
61
|
+
directions: [:UP] # Only upward moves
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Returns only high-quality opportunities
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Integration with Strategy Generator
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
generator = SQA::StrategyGenerator.new(
|
|
71
|
+
stock: stock,
|
|
72
|
+
min_gain_percent: 10.0,
|
|
73
|
+
fpop: 10, # Use 10-period lookahead
|
|
74
|
+
max_fpl_risk: 20.0 # Filter by max acceptable risk
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
patterns = generator.discover_patterns
|
|
78
|
+
# Only includes patterns with acceptable risk/reward
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## DataFrame Methods
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# Convenient DataFrame extensions
|
|
85
|
+
df = stock.df
|
|
86
|
+
|
|
87
|
+
# Add FPL columns
|
|
88
|
+
df_with_fpl = df.fpl(fpop: 10)
|
|
89
|
+
|
|
90
|
+
# Add full analysis
|
|
91
|
+
df_with_analysis = df.fpl_analysis(fpop: 10)
|
|
92
|
+
|
|
93
|
+
# Now has columns: fpl_min, fpl_max, fpl_risk, fpl_direction, etc.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Use Cases
|
|
97
|
+
|
|
98
|
+
### 1. Opportunity Screening
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
stocks = ['AAPL', 'GOOGL', 'MSFT']
|
|
102
|
+
|
|
103
|
+
stocks.each do |ticker|
|
|
104
|
+
stock = SQA::Stock.new(ticker: ticker)
|
|
105
|
+
prices = stock.df["adj_close_price"].to_a
|
|
106
|
+
|
|
107
|
+
analysis = SQA::FPOP.fpl_analysis(prices, fpop: 10)
|
|
108
|
+
|
|
109
|
+
if analysis[:reward] > 15.0 && analysis[:risk] < 10.0
|
|
110
|
+
puts "#{ticker}: Good risk/reward (#{analysis[:reward]}/#{analysis[:risk]})"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 2. Entry Point Validation
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
# Check if current point offers good risk/reward
|
|
119
|
+
current_analysis = SQA::FPOP.fpl_analysis(prices, fpop: 10)
|
|
120
|
+
|
|
121
|
+
if current_analysis[:direction] == :UP &&
|
|
122
|
+
current_analysis[:quality_score] > 0.7
|
|
123
|
+
puts "Strong buy opportunity"
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 3. Stop Loss Calculation
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
analysis = SQA::FPOP.fpl_analysis(prices, fpop: 10)
|
|
131
|
+
|
|
132
|
+
# Set stop loss based on expected risk
|
|
133
|
+
entry_price = prices.last
|
|
134
|
+
stop_loss = entry_price * (1 - analysis[:risk] / 100)
|
|
135
|
+
|
|
136
|
+
puts "Entry: $#{entry_price}"
|
|
137
|
+
puts "Stop Loss: $#{stop_loss}"
|
|
138
|
+
puts "Target: $#{entry_price * (1 + analysis[:reward] / 100)}"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Parameters
|
|
142
|
+
|
|
143
|
+
- `fpop`: Future periods to analyze (default: 10)
|
|
144
|
+
- `min_magnitude`: Minimum move size to consider (%)
|
|
145
|
+
- `max_risk`: Maximum acceptable downside (%)
|
|
146
|
+
- `directions`: Filter by `:UP`, `:DOWN`, or `:FLAT`
|
|
147
|
+
|
|
148
|
+
## Related
|
|
149
|
+
|
|
150
|
+
- [Strategy Generator](strategy-generator.md) - Uses FPOP for pattern quality
|
|
151
|
+
- [Risk Management](risk-management.md) - Position sizing based on risk
|
|
152
|
+
- [Backtesting](backtesting.md) - Validate FPOP predictions
|
|
153
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Advanced Features
|
|
2
|
+
|
|
3
|
+
Explore SQA's advanced capabilities for professional-grade analysis.
|
|
4
|
+
|
|
5
|
+
## Power Tools for Serious Analysis
|
|
6
|
+
|
|
7
|
+
SQA goes beyond basic technical analysis with powerful features for portfolio management, strategy optimization, and real-time trading.
|
|
8
|
+
|
|
9
|
+
## Available Features
|
|
10
|
+
|
|
11
|
+
### Portfolio Management
|
|
12
|
+
Track positions, calculate P&L, manage commissions, and monitor portfolio performance.
|
|
13
|
+
|
|
14
|
+
**Key Capabilities:**
|
|
15
|
+
- Position tracking with share quantities
|
|
16
|
+
- Commission and fee calculations
|
|
17
|
+
- Realized and unrealized P&L
|
|
18
|
+
- Portfolio value over time
|
|
19
|
+
|
|
20
|
+
[Learn more →](portfolio.md)
|
|
21
|
+
|
|
22
|
+
### Backtesting Framework
|
|
23
|
+
Simulate trading strategies on historical data with comprehensive performance metrics.
|
|
24
|
+
|
|
25
|
+
**Performance Metrics:**
|
|
26
|
+
- Total return and annualized return
|
|
27
|
+
- Sharpe ratio and Sortino ratio
|
|
28
|
+
- Maximum drawdown
|
|
29
|
+
- Win rate and profit factor
|
|
30
|
+
|
|
31
|
+
[Learn more →](backtesting.md)
|
|
32
|
+
|
|
33
|
+
### Strategy Generator
|
|
34
|
+
Reverse-engineer profitable trades to discover patterns and generate new strategies.
|
|
35
|
+
|
|
36
|
+
**Workflow:**
|
|
37
|
+
1. Identify profitable entry/exit points
|
|
38
|
+
2. Capture indicator states at those points
|
|
39
|
+
3. Mine patterns from indicator combinations
|
|
40
|
+
4. Generate executable strategies
|
|
41
|
+
|
|
42
|
+
[Learn more →](strategy-generator.md)
|
|
43
|
+
|
|
44
|
+
### Genetic Programming
|
|
45
|
+
Evolve optimal strategy parameters using genetic algorithms.
|
|
46
|
+
|
|
47
|
+
**Process:**
|
|
48
|
+
- Define parameter space (genes)
|
|
49
|
+
- Create random population
|
|
50
|
+
- Evaluate fitness via backtesting
|
|
51
|
+
- Evolve through selection, crossover, mutation
|
|
52
|
+
|
|
53
|
+
[Learn more →](../genetic_programming.md)
|
|
54
|
+
|
|
55
|
+
### Real-Time Streaming
|
|
56
|
+
Process live price data with event callbacks and parallel strategy execution.
|
|
57
|
+
|
|
58
|
+
**Features:**
|
|
59
|
+
- Rolling window of recent data
|
|
60
|
+
- On-the-fly indicator calculations
|
|
61
|
+
- Multiple strategies in parallel
|
|
62
|
+
- Customizable callbacks
|
|
63
|
+
|
|
64
|
+
[Learn more →](streaming.md)
|
|
65
|
+
|
|
66
|
+
### FPOP Analysis
|
|
67
|
+
Future Period of Performance analysis for risk/reward calculations.
|
|
68
|
+
|
|
69
|
+
**Capabilities:**
|
|
70
|
+
- Calculate future returns
|
|
71
|
+
- Direction classification
|
|
72
|
+
- Risk/reward metrics
|
|
73
|
+
- Performance attribution
|
|
74
|
+
|
|
75
|
+
[Learn more →](fpop.md)
|
|
76
|
+
|
|
77
|
+
## Example Workflow
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
graph LR
|
|
81
|
+
A[Historical Data] --> B[Strategy Generator]
|
|
82
|
+
B --> C[Discovered Patterns]
|
|
83
|
+
C --> D[Generated Strategies]
|
|
84
|
+
D --> E[Genetic Programming]
|
|
85
|
+
E --> F[Optimized Parameters]
|
|
86
|
+
F --> G[Backtest]
|
|
87
|
+
G --> H{Results Good?}
|
|
88
|
+
H -->|Yes| I[Live Stream]
|
|
89
|
+
H -->|No| D
|
|
90
|
+
I --> J[Portfolio Management]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Use Cases
|
|
94
|
+
|
|
95
|
+
### Quantitative Research
|
|
96
|
+
- Pattern discovery in historical data
|
|
97
|
+
- Parameter optimization
|
|
98
|
+
- Strategy validation
|
|
99
|
+
|
|
100
|
+
### Algorithmic Trading (Educational)
|
|
101
|
+
- Real-time signal generation
|
|
102
|
+
- Portfolio tracking
|
|
103
|
+
- Performance monitoring
|
|
104
|
+
|
|
105
|
+
### Risk Management
|
|
106
|
+
- Drawdown analysis
|
|
107
|
+
- Volatility tracking
|
|
108
|
+
- Position sizing
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
**Remember**: These are powerful tools for education and research. Always test thoroughly before considering any real trading applications.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Multi-Timeframe Analysis
|
|
2
|
+
|
|
3
|
+
Analyze multiple timeframes simultaneously for better trade timing.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
mta = SQA::MultiTimeframe.new(stock: stock)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Trend Alignment
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
alignment = mta.trend_alignment
|
|
15
|
+
|
|
16
|
+
# {
|
|
17
|
+
# daily: :up,
|
|
18
|
+
# weekly: :up,
|
|
19
|
+
# monthly: :up,
|
|
20
|
+
# aligned: true,
|
|
21
|
+
# direction: :bullish
|
|
22
|
+
# }
|
|
23
|
+
|
|
24
|
+
if alignment[:aligned] && alignment[:direction] == :bullish
|
|
25
|
+
puts "Strong uptrend across all timeframes"
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Multi-Timeframe Signal
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
# Higher timeframe = trend, lower timeframe = timing
|
|
33
|
+
signal = mta.signal(
|
|
34
|
+
strategy_class: SQA::Strategy::RSI,
|
|
35
|
+
higher_timeframe: :weekly, # Trend filter
|
|
36
|
+
lower_timeframe: :daily # Entry timing
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Only takes trades aligned with higher timeframe trend
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Support/Resistance
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# Find levels that appear across multiple timeframes
|
|
46
|
+
levels = mta.support_resistance(tolerance: 0.02)
|
|
47
|
+
|
|
48
|
+
levels.each do |level|
|
|
49
|
+
puts "Strong level at #{level[:price]}"
|
|
50
|
+
puts " Appears in: #{level[:timeframes].join(', ')}"
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Divergence Detection
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
# Price makes new high but indicator doesn't (bearish)
|
|
58
|
+
divergence = mta.detect_divergence(
|
|
59
|
+
indicator: :rsi,
|
|
60
|
+
lookback: 20
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if divergence[:type] == :bearish
|
|
64
|
+
puts "Bearish divergence detected - potential reversal"
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Pattern Matcher
|
|
2
|
+
|
|
3
|
+
Pattern recognition and similarity search for forecasting.
|
|
4
|
+
|
|
5
|
+
## Find Similar Patterns
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
matcher = SQA::PatternMatcher.new(stock: stock)
|
|
9
|
+
|
|
10
|
+
# Find historical patterns similar to current
|
|
11
|
+
similar = matcher.find_similar(
|
|
12
|
+
lookback: 10, # 10-period pattern
|
|
13
|
+
num_matches: 5, # Find 5 best matches
|
|
14
|
+
method: :euclidean # Distance metric
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
similar.each do |match|
|
|
18
|
+
puts "Distance: #{match[:distance]}"
|
|
19
|
+
puts "Future return: #{match[:future_return]}%"
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Distance Methods
|
|
24
|
+
|
|
25
|
+
- `:euclidean` - Standard distance
|
|
26
|
+
- `:dtw` - Dynamic Time Warping (handles compression/stretching)
|
|
27
|
+
- `:correlation` - Correlation-based similarity
|
|
28
|
+
|
|
29
|
+
## Forecasting
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
forecast = matcher.forecast(
|
|
33
|
+
lookback: 10,
|
|
34
|
+
forecast_periods: 5,
|
|
35
|
+
method: :dtw
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
puts "Forecast price: #{forecast[:forecast_price]}"
|
|
39
|
+
puts "Expected return: #{forecast[:forecast_return]}%"
|
|
40
|
+
puts "95% CI: #{forecast[:confidence_interval_95]}"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Chart Pattern Detection
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# Detect double top/bottom
|
|
47
|
+
patterns = matcher.detect_chart_pattern(:double_top)
|
|
48
|
+
|
|
49
|
+
# Detect head & shoulders
|
|
50
|
+
patterns = matcher.detect_chart_pattern(:head_and_shoulders)
|
|
51
|
+
|
|
52
|
+
# Detect triangles
|
|
53
|
+
patterns = matcher.detect_chart_pattern(:triangle)
|
|
54
|
+
|
|
55
|
+
patterns.each do |pattern|
|
|
56
|
+
puts "#{pattern[:type]} at index #{pattern[:index]}"
|
|
57
|
+
puts "Confidence: #{pattern[:confidence]}"
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Pattern Clustering
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# Group similar patterns
|
|
65
|
+
clusters = matcher.cluster_patterns(
|
|
66
|
+
num_clusters: 5,
|
|
67
|
+
lookback: 10
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
clusters.each_with_index do |cluster, i|
|
|
71
|
+
puts "Cluster #{i}: #{cluster.size} patterns"
|
|
72
|
+
puts " Avg future return: #{cluster[:avg_return]}%"
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Portfolio Optimizer
|
|
2
|
+
|
|
3
|
+
Multi-objective portfolio optimization for optimal asset allocation and rebalancing.
|
|
4
|
+
|
|
5
|
+
## Maximum Sharpe Portfolio
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Get returns for multiple stocks
|
|
9
|
+
returns_matrix = ['AAPL', 'GOOGL', 'MSFT'].map do |ticker|
|
|
10
|
+
stock = SQA::Stock.new(ticker: ticker)
|
|
11
|
+
prices = stock.df["adj_close_price"].to_a
|
|
12
|
+
prices.each_cons(2).map { |a, b| (b - a) / a }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Find optimal weights
|
|
16
|
+
result = SQA::PortfolioOptimizer.maximum_sharpe(returns_matrix)
|
|
17
|
+
|
|
18
|
+
puts "Optimal Weights:"
|
|
19
|
+
puts " AAPL: #{(result[:weights][0] * 100).round(2)}%"
|
|
20
|
+
puts " GOOGL: #{(result[:weights][1] * 100).round(2)}%"
|
|
21
|
+
puts " MSFT: #{(result[:weights][2] * 100).round(2)}%"
|
|
22
|
+
puts "\nExpected Sharpe: #{result[:sharpe].round(2)}"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Minimum Variance Portfolio
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
result = SQA::PortfolioOptimizer.minimum_variance(returns_matrix)
|
|
29
|
+
# Lowest risk allocation
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Risk Parity
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
result = SQA::PortfolioOptimizer.risk_parity(returns_matrix)
|
|
36
|
+
# Equal risk contribution from each asset
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Efficient Frontier
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
frontier = SQA::PortfolioOptimizer.efficient_frontier(
|
|
43
|
+
returns_matrix,
|
|
44
|
+
num_portfolios: 50
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
frontier.each do |portfolio|
|
|
48
|
+
puts "Return: #{portfolio[:return]}, Risk: #{portfolio[:volatility]}"
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Multi-Objective Optimization
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
result = SQA::PortfolioOptimizer.multi_objective(
|
|
56
|
+
returns_matrix,
|
|
57
|
+
objectives: {
|
|
58
|
+
maximize_return: 0.4,
|
|
59
|
+
minimize_volatility: 0.3,
|
|
60
|
+
minimize_drawdown: 0.3
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Rebalancing
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
current_weights = [0.5, 0.3, 0.2]
|
|
69
|
+
target_weights = [0.4, 0.4, 0.2]
|
|
70
|
+
|
|
71
|
+
trades = SQA::PortfolioOptimizer.rebalance(
|
|
72
|
+
current_weights: current_weights,
|
|
73
|
+
target_weights: target_weights,
|
|
74
|
+
portfolio_value: 10_000
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# trades => [{ ticker: 'AAPL', shares: -5, value: -750 }, ...]
|
|
78
|
+
```
|
|
79
|
+
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Portfolio Management
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Portfolio class tracks positions, calculates P&L, manages commissions, and monitors portfolio performance over time.
|
|
6
|
+
|
|
7
|
+
## Creating a Portfolio
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
require 'sqa'
|
|
11
|
+
|
|
12
|
+
portfolio = SQA::Portfolio.new(
|
|
13
|
+
initial_cash: 10_000, # Starting capital
|
|
14
|
+
commission: 1.0 # Commission per trade
|
|
15
|
+
)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic Operations
|
|
19
|
+
|
|
20
|
+
### Buy Stock
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
24
|
+
# Deducts: (10 × $150) + $1 commission = $1,501
|
|
25
|
+
|
|
26
|
+
puts portfolio.cash # => 8,499.0
|
|
27
|
+
puts portfolio.positions['AAPL'] # => 10 shares
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Sell Stock
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
portfolio.sell('AAPL', shares: 5, price: 160.0)
|
|
34
|
+
# Adds: (5 × $160) - $1 commission = $799
|
|
35
|
+
|
|
36
|
+
puts portfolio.cash # => 9,298.0
|
|
37
|
+
puts portfolio.positions['AAPL'] # => 5 shares
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Calculate Portfolio Value
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
current_prices = {
|
|
44
|
+
'AAPL' => 165.0,
|
|
45
|
+
'GOOGL' => 2800.0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
total_value = portfolio.value(current_prices)
|
|
49
|
+
# Cash + (positions × current prices)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Complete Example
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# Initialize
|
|
56
|
+
portfolio = SQA::Portfolio.new(initial_cash: 10_000)
|
|
57
|
+
|
|
58
|
+
# Buy multiple stocks
|
|
59
|
+
portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
60
|
+
portfolio.buy('GOOGL', shares: 2, price: 2750.0)
|
|
61
|
+
|
|
62
|
+
# Check positions
|
|
63
|
+
puts "Positions: #{portfolio.positions}"
|
|
64
|
+
# => {"AAPL"=>10, "GOOGL"=>2}
|
|
65
|
+
|
|
66
|
+
# Calculate current value
|
|
67
|
+
prices = { 'AAPL' => 160.0, 'GOOGL' => 2800.0 }
|
|
68
|
+
puts "Portfolio Value: $#{portfolio.value(prices)}"
|
|
69
|
+
|
|
70
|
+
# Sell some shares
|
|
71
|
+
portfolio.sell('AAPL', shares: 5, price: 160.0)
|
|
72
|
+
|
|
73
|
+
# View trade history
|
|
74
|
+
portfolio.trades.each do |trade|
|
|
75
|
+
puts "#{trade.action} #{trade.shares} #{trade.ticker} @ $#{trade.price}"
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## P&L Tracking
|
|
80
|
+
|
|
81
|
+
### Realized P&L
|
|
82
|
+
Profit/loss from closed positions (sold shares).
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
# Buy 10 shares @ $150
|
|
86
|
+
portfolio.buy('AAPL', shares: 10, price: 150.0)
|
|
87
|
+
|
|
88
|
+
# Sell 5 shares @ $160
|
|
89
|
+
portfolio.sell('AAPL', shares: 5, price: 160.0)
|
|
90
|
+
|
|
91
|
+
# Realized P&L: (160 - 150) × 5 = $50
|
|
92
|
+
# (minus 2 × $1 commission = $48)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Unrealized P&L
|
|
96
|
+
Paper profit/loss on open positions.
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Still holding 5 shares bought @ $150
|
|
100
|
+
# Current price: $165
|
|
101
|
+
|
|
102
|
+
unrealized_pnl = (165.0 - 150.0) * 5 # => $75
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Portfolio Methods
|
|
106
|
+
|
|
107
|
+
| Method | Description |
|
|
108
|
+
|--------|-------------|
|
|
109
|
+
| `buy(ticker, shares:, price:)` | Purchase shares |
|
|
110
|
+
| `sell(ticker, shares:, price:)` | Sell shares |
|
|
111
|
+
| `value(current_prices)` | Calculate total portfolio value |
|
|
112
|
+
| `positions` | Hash of ticker → share count |
|
|
113
|
+
| `trades` | Array of all trades |
|
|
114
|
+
| `cash` | Available cash balance |
|
|
115
|
+
|
|
116
|
+
## Usage with Backtesting
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
backtest = SQA::Backtest.new(
|
|
120
|
+
stock: stock,
|
|
121
|
+
strategy: SQA::Strategy::RSI,
|
|
122
|
+
initial_cash: 10_000,
|
|
123
|
+
commission: 1.0
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
results = backtest.run
|
|
127
|
+
|
|
128
|
+
puts "Final Value: $#{results.portfolio_value}"
|
|
129
|
+
puts "Total Return: #{results.total_return}%"
|
|
130
|
+
puts "Total Trades: #{results.num_trades}"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Advanced Features
|
|
134
|
+
|
|
135
|
+
### Position Sizing
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
# Risk-based position sizing
|
|
139
|
+
risk_per_trade = portfolio.cash * 0.02 # 2% risk
|
|
140
|
+
shares = (risk_per_trade / stop_loss_distance).to_i
|
|
141
|
+
|
|
142
|
+
portfolio.buy('AAPL', shares: shares, price: current_price)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Diversification Check
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
def check_diversification(portfolio, prices)
|
|
149
|
+
total_value = portfolio.value(prices)
|
|
150
|
+
|
|
151
|
+
portfolio.positions.each do |ticker, shares|
|
|
152
|
+
position_value = shares * prices[ticker]
|
|
153
|
+
percentage = (position_value / total_value) * 100
|
|
154
|
+
|
|
155
|
+
puts "#{ticker}: #{percentage.round(2)}%"
|
|
156
|
+
warn "⚠️ #{ticker} exceeds 20%" if percentage > 20
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Related
|
|
162
|
+
|
|
163
|
+
- [Backtesting](backtesting.md) - Test strategies with portfolio tracking
|
|
164
|
+
- [Risk Management](risk-management.md) - Position sizing and risk metrics
|
|
165
|
+
- [Portfolio Optimizer](portfolio-optimizer.md) - Optimal asset allocation
|
|
166
|
+
|