ollama-client 0.2.0 → 0.2.1
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/examples/advanced_multi_step_agent.rb +95 -18
- data/examples/dhanhq/README.md +236 -0
- data/examples/dhanhq/agents/base_agent.rb +76 -0
- data/examples/dhanhq/agents/data_agent.rb +66 -0
- data/examples/dhanhq/agents/orchestrator_agent.rb +121 -0
- data/examples/dhanhq/agents/technical_analysis_agent.rb +234 -0
- data/examples/dhanhq/agents/trading_agent.rb +81 -0
- data/examples/dhanhq/analysis/market_structure.rb +122 -0
- data/examples/dhanhq/analysis/pattern_recognizer.rb +175 -0
- data/examples/dhanhq/analysis/trend_analyzer.rb +90 -0
- data/examples/dhanhq/builders/market_context_builder.rb +67 -0
- data/examples/dhanhq/dhanhq_agent.rb +632 -0
- data/examples/dhanhq/indicators/technical_indicators.rb +160 -0
- data/examples/dhanhq/scanners/intraday_options_scanner.rb +387 -0
- data/examples/dhanhq/scanners/swing_scanner.rb +213 -0
- data/examples/dhanhq/schemas/agent_schemas.rb +61 -0
- data/examples/dhanhq/services/base_service.rb +46 -0
- data/examples/dhanhq/services/data_service.rb +120 -0
- data/examples/dhanhq/services/trading_service.rb +62 -0
- data/examples/dhanhq/technical_analysis_agentic_runner.rb +278 -0
- data/examples/dhanhq/technical_analysis_runner.rb +366 -0
- data/examples/dhanhq/utils/instrument_helper.rb +32 -0
- data/examples/dhanhq/utils/parameter_cleaner.rb +28 -0
- data/examples/dhanhq/utils/parameter_normalizer.rb +45 -0
- data/examples/dhanhq/utils/rate_limiter.rb +23 -0
- data/examples/dhanhq/utils/trading_parameter_normalizer.rb +77 -0
- data/examples/dhanhq_agent.rb +220 -51
- data/examples/dhanhq_tools.rb +314 -121
- data/examples/tool_calling_pattern.rb +4 -1
- data/lib/ollama/version.rb +1 -1
- metadata +27 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6b1ed7c3f2d09174f4127c92b0b22eef90fc3deb895dc86183d296a6a70cc3b4
|
|
4
|
+
data.tar.gz: f2d0737f5cbb77557fa736fb45f7a5c7c94fd939d1005c53d68e82d229844283
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04dcc1b56d68d715727f5f51bd9511d1c4b633369435e4f95008b2bb826d988d00df6fa307dc900fb0c8365b6f4bc0e111e17b4c74deb9c25360bfa36f6148cd
|
|
7
|
+
data.tar.gz: 8ea4bef981e5601dddd258a1f0dab481e76601f765bddc9783098bedc144cd1d0ceeb9e30b185541fa688ea8519fc796356a9ffe6abbc437a7dae6d93b757852
|
|
@@ -118,6 +118,45 @@ class MultiStepAgent
|
|
|
118
118
|
break
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
+
# Prevent infinite loops - if we've done the same action 3+ times, force progression
|
|
122
|
+
recent_actions = @state[:steps_completed].last(3).map { |s| s[:action] }
|
|
123
|
+
if recent_actions.length == 3 && recent_actions.uniq.length == 1
|
|
124
|
+
puts "⚠️ Detected repetitive actions - forcing workflow progression"
|
|
125
|
+
# Force next phase
|
|
126
|
+
if recent_actions.first == "collect"
|
|
127
|
+
puts " → Moving to analysis phase"
|
|
128
|
+
decision["action"]["type"] = "analyze"
|
|
129
|
+
decision["action"]["parameters"] = { "target" => "collected_data" }
|
|
130
|
+
result = execute_action(decision)
|
|
131
|
+
@state[:steps_completed] << {
|
|
132
|
+
step: decision["step"],
|
|
133
|
+
action: "analyze",
|
|
134
|
+
result: result
|
|
135
|
+
}
|
|
136
|
+
elsif recent_actions.first == "analyze"
|
|
137
|
+
puts " → Moving to validation phase"
|
|
138
|
+
decision["action"]["type"] = "validate"
|
|
139
|
+
decision["action"]["parameters"] = { "type" => "results" }
|
|
140
|
+
result = execute_action(decision)
|
|
141
|
+
@state[:steps_completed] << {
|
|
142
|
+
step: decision["step"],
|
|
143
|
+
action: "validate",
|
|
144
|
+
result: result
|
|
145
|
+
}
|
|
146
|
+
elsif recent_actions.first == "validate"
|
|
147
|
+
puts " → Completing workflow"
|
|
148
|
+
decision["action"]["type"] = "complete"
|
|
149
|
+
result = execute_action(decision)
|
|
150
|
+
@state[:steps_completed] << {
|
|
151
|
+
step: decision["step"],
|
|
152
|
+
action: "complete",
|
|
153
|
+
result: result
|
|
154
|
+
}
|
|
155
|
+
puts "\n✅ Workflow completed successfully!"
|
|
156
|
+
break
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
121
160
|
# Handle risk
|
|
122
161
|
if decision["risk_assessment"] && decision["risk_assessment"]["level"] == "high"
|
|
123
162
|
puts "⚠️ High risk detected - proceeding with caution"
|
|
@@ -147,31 +186,61 @@ class MultiStepAgent
|
|
|
147
186
|
|
|
148
187
|
private
|
|
149
188
|
|
|
150
|
-
def build_context(
|
|
189
|
+
def build_context(goal:) # rubocop:disable Lint/UnusedMethodArgument
|
|
151
190
|
{
|
|
152
191
|
steps_completed: @state[:steps_completed].map { |s| s[:action] },
|
|
153
192
|
data_collected: @state[:data_collected].keys,
|
|
154
|
-
error_count: @state[:errors].length
|
|
193
|
+
error_count: @state[:errors].length,
|
|
194
|
+
step_count: @state[:steps_completed].length
|
|
155
195
|
}
|
|
156
196
|
end
|
|
157
197
|
|
|
158
198
|
def build_prompt(goal:, context:)
|
|
199
|
+
step_count = context[:step_count]
|
|
200
|
+
completed_actions = context[:steps_completed]
|
|
201
|
+
collected_data = context[:data_collected]
|
|
202
|
+
|
|
203
|
+
# Determine current phase based on what's been done
|
|
204
|
+
phase = if completed_actions.empty?
|
|
205
|
+
"collection"
|
|
206
|
+
elsif completed_actions.include?("collect") && !completed_actions.include?("analyze")
|
|
207
|
+
"analysis"
|
|
208
|
+
elsif completed_actions.include?("analyze") && !completed_actions.include?("validate")
|
|
209
|
+
"validation"
|
|
210
|
+
else
|
|
211
|
+
"completion"
|
|
212
|
+
end
|
|
213
|
+
|
|
159
214
|
<<~PROMPT
|
|
160
215
|
Goal: #{goal}
|
|
161
216
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
1.
|
|
170
|
-
2.
|
|
171
|
-
3.
|
|
172
|
-
4.
|
|
217
|
+
Current Phase: #{phase}
|
|
218
|
+
Steps completed: #{step_count}
|
|
219
|
+
Actions taken: #{completed_actions.join(", ") || "none"}
|
|
220
|
+
Data collected: #{collected_data.join(", ") || "none"}
|
|
221
|
+
Errors encountered: #{context[:error_count]}
|
|
222
|
+
|
|
223
|
+
Workflow Phases (in order):
|
|
224
|
+
1. COLLECTION: Collect initial data (user data, patterns, etc.)
|
|
225
|
+
2. ANALYSIS: Analyze collected data for patterns and insights
|
|
226
|
+
3. VALIDATION: Validate the analysis results
|
|
227
|
+
4. COMPLETION: Finish the workflow
|
|
228
|
+
|
|
229
|
+
Current State Analysis:
|
|
230
|
+
- You are in the #{phase} phase
|
|
231
|
+
- You have completed #{step_count} steps
|
|
232
|
+
- You have collected: #{collected_data.any? ? collected_data.join(", ") : "nothing yet"}
|
|
233
|
+
|
|
234
|
+
Decision Guidelines:
|
|
235
|
+
- If in COLLECTION phase and no data collected: use action "collect" with specific data_type (e.g., "user_data", "patterns")
|
|
236
|
+
- If data collected but not analyzed: use action "analyze" with target
|
|
237
|
+
- If analyzed but not validated: use action "validate"
|
|
238
|
+
- If all phases done: use action "complete"
|
|
239
|
+
- AVOID repeating the same action multiple times unless necessary
|
|
240
|
+
- Progress through phases: collect → analyze → validate → complete
|
|
173
241
|
|
|
174
242
|
Provide a structured decision with high confidence (>0.7) if possible.
|
|
243
|
+
Set step number to #{step_count + 1}.
|
|
175
244
|
PROMPT
|
|
176
245
|
end
|
|
177
246
|
|
|
@@ -198,15 +267,21 @@ class MultiStepAgent
|
|
|
198
267
|
|
|
199
268
|
case action_type
|
|
200
269
|
when "collect"
|
|
201
|
-
data_key = params["data_type"] || "
|
|
270
|
+
data_key = params["data_type"] || params["key"] || "user_data"
|
|
271
|
+
# Prevent collecting the same generic data repeatedly
|
|
272
|
+
if @state[:data_collected].key?(data_key) && data_key.match?(/^(missing|unknown|data)$/i) && !@state[:data_collected].key?("user_data")
|
|
273
|
+
data_key = "user_data"
|
|
274
|
+
end
|
|
202
275
|
puts " 📥 Collecting: #{data_key}"
|
|
203
276
|
@state[:data_collected][data_key] = "collected_at_#{Time.now.to_i}"
|
|
204
277
|
{ status: "collected", key: data_key }
|
|
205
278
|
|
|
206
279
|
when "analyze"
|
|
207
|
-
target = params["target"] || "
|
|
280
|
+
target = params["target"] || "collected_data"
|
|
208
281
|
puts " 🔍 Analyzing: #{target}"
|
|
209
|
-
|
|
282
|
+
# Mark that analysis has been done
|
|
283
|
+
@state[:data_collected]["analysis_complete"] = true
|
|
284
|
+
{ status: "analyzed", target: target, insights: "Patterns identified in collected data" }
|
|
210
285
|
|
|
211
286
|
when "transform"
|
|
212
287
|
transformation = params["type"] || "default"
|
|
@@ -214,9 +289,11 @@ class MultiStepAgent
|
|
|
214
289
|
{ status: "transformed", type: transformation }
|
|
215
290
|
|
|
216
291
|
when "validate"
|
|
217
|
-
validation_type = params["type"] || "
|
|
292
|
+
validation_type = params["type"] || "results"
|
|
218
293
|
puts " ✓ Validating: #{validation_type}"
|
|
219
|
-
|
|
294
|
+
# Mark that validation has been done
|
|
295
|
+
@state[:data_collected]["validation_complete"] = true
|
|
296
|
+
{ status: "validated", type: validation_type, result: "All checks passed" }
|
|
220
297
|
|
|
221
298
|
when "complete"
|
|
222
299
|
puts " ✅ Completing workflow"
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# DhanHQ Agent - Complete Trading & Technical Analysis System
|
|
2
|
+
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
### Core Capabilities
|
|
6
|
+
- **Data Retrieval**: 6 DhanHQ Data APIs (Market Quote, LTP, Depth, Historical, Options, Chain)
|
|
7
|
+
- **Trading Tools**: Order parameter building (regular, super orders, cancel)
|
|
8
|
+
- **LLM-Powered Decisions**: Ollama for intelligent market analysis and decision making
|
|
9
|
+
- **LLM Orchestration**: AI decides what symbols to analyze, which exchange segments, and what analysis to run
|
|
10
|
+
|
|
11
|
+
### Technical Analysis (NEW!)
|
|
12
|
+
- **Trend Analysis**: Uptrend, downtrend, sideways detection with strength calculation
|
|
13
|
+
- **Market Structure**: SMC (Smart Money Concepts) analysis
|
|
14
|
+
- Order blocks identification
|
|
15
|
+
- Liquidity zones (buy-side/sell-side)
|
|
16
|
+
- Structure breaks detection
|
|
17
|
+
- **Pattern Recognition**:
|
|
18
|
+
- Candlestick patterns (engulfing, hammer, shooting star, etc.)
|
|
19
|
+
- Chart patterns (head & shoulders, double top/bottom)
|
|
20
|
+
- **Technical Indicators**:
|
|
21
|
+
- Moving Averages (SMA, EMA)
|
|
22
|
+
- RSI (Relative Strength Index)
|
|
23
|
+
- MACD (Moving Average Convergence Divergence)
|
|
24
|
+
- Bollinger Bands
|
|
25
|
+
- ATR (Average True Range)
|
|
26
|
+
- Support & Resistance levels
|
|
27
|
+
|
|
28
|
+
### Scanners (NEW!)
|
|
29
|
+
- **Swing Trading Scanner**: Find swing trading candidates based on technical analysis
|
|
30
|
+
- **Intraday Options Scanner**: Find intraday options buying opportunities with IV/OI analysis
|
|
31
|
+
|
|
32
|
+
## Structure
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
dhanhq/
|
|
36
|
+
├── agents/ # Agent classes (LLM decision makers)
|
|
37
|
+
│ ├── base_agent.rb
|
|
38
|
+
│ ├── data_agent.rb
|
|
39
|
+
│ ├── trading_agent.rb
|
|
40
|
+
│ ├── technical_analysis_agent.rb
|
|
41
|
+
│ └── orchestrator_agent.rb # NEW - LLM decides what to analyze
|
|
42
|
+
├── services/ # Service classes (API executors)
|
|
43
|
+
│ ├── base_service.rb
|
|
44
|
+
│ ├── data_service.rb
|
|
45
|
+
│ └── trading_service.rb
|
|
46
|
+
├── analysis/ # Technical analysis modules (NEW)
|
|
47
|
+
│ ├── market_structure.rb # SMC, trend, structure breaks
|
|
48
|
+
│ ├── pattern_recognizer.rb # Candlestick & chart patterns
|
|
49
|
+
│ └── trend_analyzer.rb # Comprehensive trend analysis
|
|
50
|
+
├── indicators/ # Technical indicators (NEW)
|
|
51
|
+
│ └── technical_indicators.rb # RSI, MACD, MA, Bollinger, ATR, etc.
|
|
52
|
+
├── scanners/ # Trading scanners (NEW)
|
|
53
|
+
│ ├── swing_scanner.rb # Swing trading candidates
|
|
54
|
+
│ └── intraday_options_scanner.rb # Options opportunities
|
|
55
|
+
├── builders/ # Builder classes
|
|
56
|
+
│ └── market_context_builder.rb
|
|
57
|
+
├── utils/ # Utility classes
|
|
58
|
+
│ ├── instrument_helper.rb
|
|
59
|
+
│ ├── parameter_normalizer.rb
|
|
60
|
+
│ ├── parameter_cleaner.rb
|
|
61
|
+
│ ├── trading_parameter_normalizer.rb
|
|
62
|
+
│ └── rate_limiter.rb
|
|
63
|
+
├── schemas/ # JSON schemas for LLM
|
|
64
|
+
│ └── agent_schemas.rb
|
|
65
|
+
└── dhanhq_agent.rb # Main entry point
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Design Principles
|
|
69
|
+
|
|
70
|
+
- **SOLID**: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion
|
|
71
|
+
- **KISS**: Keep It Simple, Stupid
|
|
72
|
+
- **DRY**: Don't Repeat Yourself
|
|
73
|
+
- **Namespacing**: All classes under `DhanHQ` module
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
### Basic Usage
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
require_relative "dhanhq/dhanhq_agent"
|
|
81
|
+
|
|
82
|
+
agent = DhanHQ::Agent.new
|
|
83
|
+
|
|
84
|
+
# Data retrieval
|
|
85
|
+
decision = agent.data_agent.analyze_and_decide(market_context: "...")
|
|
86
|
+
result = agent.data_agent.execute_decision(decision)
|
|
87
|
+
|
|
88
|
+
# Trading
|
|
89
|
+
trading_decision = agent.trading_agent.analyze_and_decide(market_context: "...")
|
|
90
|
+
order_params = agent.trading_agent.execute_decision(trading_decision)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### LLM Orchestration (NEW!)
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# Let LLM decide what to analyze
|
|
97
|
+
plan = agent.orchestrator_agent.decide_analysis_plan(
|
|
98
|
+
market_context: "NIFTY is at 25,790, RELIANCE showing strength",
|
|
99
|
+
user_query: "Find swing trading opportunities in banking stocks"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Plan contains:
|
|
103
|
+
# - analysis_plan: Array of tasks with symbol, exchange_segment, analysis_type
|
|
104
|
+
# - reasoning: Why the LLM chose this plan
|
|
105
|
+
|
|
106
|
+
# Execute the plan
|
|
107
|
+
plan["analysis_plan"].each do |task|
|
|
108
|
+
case task["analysis_type"]
|
|
109
|
+
when "technical_analysis"
|
|
110
|
+
agent.analysis_agent.analyze_symbol(...)
|
|
111
|
+
when "swing_scan"
|
|
112
|
+
agent.swing_scanner.scan_symbols(...)
|
|
113
|
+
when "options_scan"
|
|
114
|
+
agent.options_scanner.scan_for_options_setups(...)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Technical Analysis
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# Analyze a symbol
|
|
123
|
+
analysis = agent.analysis_agent.analyze_symbol(
|
|
124
|
+
symbol: "RELIANCE",
|
|
125
|
+
exchange_segment: "NSE_EQ"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Get swing trading recommendation
|
|
129
|
+
recommendation = agent.analysis_agent.generate_recommendation(
|
|
130
|
+
analysis,
|
|
131
|
+
trading_style: :swing
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Scanning
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
# Swing trading scanner
|
|
139
|
+
candidates = agent.swing_scanner.scan_symbols(
|
|
140
|
+
["RELIANCE", "TCS", "INFY"],
|
|
141
|
+
exchange_segment: "NSE_EQ"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Intraday options scanner
|
|
145
|
+
options_setups = agent.options_scanner.scan_for_options_setups(
|
|
146
|
+
"NIFTY",
|
|
147
|
+
exchange_segment: "IDX_I"
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Technical Analysis Capabilities
|
|
152
|
+
|
|
153
|
+
### Trend Analysis
|
|
154
|
+
- Detects uptrend, downtrend, sideways
|
|
155
|
+
- Calculates trend strength percentage
|
|
156
|
+
- Uses moving averages for confirmation
|
|
157
|
+
|
|
158
|
+
### SMC (Smart Money Concepts)
|
|
159
|
+
- **Order Blocks**: Identifies institutional order zones
|
|
160
|
+
- **Liquidity Zones**: Finds buy-side and sell-side liquidity areas
|
|
161
|
+
- **Structure Breaks**: Detects trend changes and breakouts
|
|
162
|
+
|
|
163
|
+
### Pattern Recognition
|
|
164
|
+
- **Candlestick Patterns**: Engulfing, hammer, shooting star, three white soldiers/crows
|
|
165
|
+
- **Chart Patterns**: Head & shoulders, double top/bottom
|
|
166
|
+
|
|
167
|
+
### Indicators
|
|
168
|
+
- **RSI**: Overbought/oversold conditions
|
|
169
|
+
- **MACD**: Momentum and trend changes
|
|
170
|
+
- **Moving Averages**: Trend confirmation
|
|
171
|
+
- **Bollinger Bands**: Volatility and mean reversion
|
|
172
|
+
- **ATR**: Volatility measurement
|
|
173
|
+
- **Support/Resistance**: Key price levels
|
|
174
|
+
|
|
175
|
+
## Scanner Features
|
|
176
|
+
|
|
177
|
+
### Swing Trading Scanner
|
|
178
|
+
- Scores symbols based on:
|
|
179
|
+
- Trend strength
|
|
180
|
+
- RSI levels (prefers 40-60 zone)
|
|
181
|
+
- MACD bullish crossovers
|
|
182
|
+
- Structure breaks
|
|
183
|
+
- Pattern recognition
|
|
184
|
+
- Returns top candidates sorted by score
|
|
185
|
+
|
|
186
|
+
### Intraday Options Scanner
|
|
187
|
+
- Analyzes underlying trend
|
|
188
|
+
- Finds ATM/near-ATM strikes
|
|
189
|
+
- Considers:
|
|
190
|
+
- Implied Volatility (lower is better for buying)
|
|
191
|
+
- Open Interest (higher is better)
|
|
192
|
+
- Volume (higher is better)
|
|
193
|
+
- Trend alignment
|
|
194
|
+
- RSI levels
|
|
195
|
+
- Returns top 5 setups with scores
|
|
196
|
+
|
|
197
|
+
## Running
|
|
198
|
+
|
|
199
|
+
### Full Demo (All Features)
|
|
200
|
+
```bash
|
|
201
|
+
ruby examples/dhanhq/dhanhq_agent.rb
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This will demonstrate:
|
|
205
|
+
1. Data retrieval with LLM decisions
|
|
206
|
+
2. Trading order parameter building
|
|
207
|
+
3. Technical analysis for a symbol
|
|
208
|
+
4. Swing trading scanner
|
|
209
|
+
5. Intraday options scanner
|
|
210
|
+
|
|
211
|
+
### Technical Analysis Only
|
|
212
|
+
```bash
|
|
213
|
+
# LLM decides what to analyze (default)
|
|
214
|
+
ruby examples/dhanhq/technical_analysis_runner.rb
|
|
215
|
+
|
|
216
|
+
# With custom query
|
|
217
|
+
ruby examples/dhanhq/technical_analysis_runner.rb "Find options opportunities for NIFTY"
|
|
218
|
+
|
|
219
|
+
# With custom query and market context
|
|
220
|
+
ruby examples/dhanhq/technical_analysis_runner.rb "Analyze banking stocks" "Banking sector showing strength"
|
|
221
|
+
|
|
222
|
+
# Show manual examples too (for comparison)
|
|
223
|
+
SHOW_MANUAL_EXAMPLES=true ruby examples/dhanhq/technical_analysis_runner.rb
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**LLM-Powered Orchestration:**
|
|
227
|
+
The LLM decides:
|
|
228
|
+
- Which symbols to analyze (e.g., "RELIANCE", "NIFTY", "BANKNIFTY")
|
|
229
|
+
- Which exchange segments to use (NSE_EQ, IDX_I, etc.)
|
|
230
|
+
- What type of analysis to run (technical_analysis, swing_scan, options_scan)
|
|
231
|
+
- Priority order of analysis tasks
|
|
232
|
+
|
|
233
|
+
This makes the system truly autonomous - you just ask what you want, and the LLM figures out how to get it.
|
|
234
|
+
|
|
235
|
+
**Manual Examples (optional):**
|
|
236
|
+
If `SHOW_MANUAL_EXAMPLES=true`, it also runs fixed examples for comparison.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../utils/parameter_normalizer"
|
|
4
|
+
require_relative "../utils/parameter_cleaner"
|
|
5
|
+
|
|
6
|
+
module DhanHQ
|
|
7
|
+
module Agents
|
|
8
|
+
# Base class for all agents with common LLM interaction logic
|
|
9
|
+
class BaseAgent
|
|
10
|
+
MIN_CONFIDENCE = 0.6
|
|
11
|
+
|
|
12
|
+
def initialize(ollama_client:, schema:)
|
|
13
|
+
@ollama_client = ollama_client
|
|
14
|
+
@schema = schema
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def analyze_and_decide(market_context:)
|
|
18
|
+
prompt = build_analysis_prompt(market_context: market_context)
|
|
19
|
+
|
|
20
|
+
decision = call_llm(prompt)
|
|
21
|
+
return invalid_decision unless valid_decision?(decision)
|
|
22
|
+
|
|
23
|
+
decision = clean_parameters(decision)
|
|
24
|
+
return low_confidence_decision(decision) if low_confidence?(decision)
|
|
25
|
+
|
|
26
|
+
decision
|
|
27
|
+
rescue Ollama::Error => e
|
|
28
|
+
error_decision(e.message)
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
error_decision(e.message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
def build_analysis_prompt(market_context:)
|
|
36
|
+
raise NotImplementedError, "Subclasses must implement build_analysis_prompt"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def call_llm(prompt)
|
|
42
|
+
@ollama_client.generate(prompt: prompt, schema: @schema)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def valid_decision?(decision)
|
|
46
|
+
decision.is_a?(Hash) && decision["confidence"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def clean_parameters(decision)
|
|
50
|
+
return decision unless decision["parameters"].is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
cleaned_params = DhanHQ::Utils::ParameterCleaner.clean(decision["parameters"])
|
|
53
|
+
decision.merge("parameters" => cleaned_params)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def low_confidence?(decision)
|
|
57
|
+
decision["confidence"] < MIN_CONFIDENCE
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def invalid_decision
|
|
61
|
+
{ action: "no_action", reason: "invalid_decision" }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def low_confidence_decision(decision)
|
|
65
|
+
confidence_pct = (decision["confidence"] * 100).round
|
|
66
|
+
puts "⚠️ Low confidence (#{confidence_pct}%) - skipping action"
|
|
67
|
+
{ action: "no_action", reason: "low_confidence" }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def error_decision(message)
|
|
71
|
+
puts "❌ Ollama error: #{message}"
|
|
72
|
+
{ action: "no_action", reason: "error", error: message }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_agent"
|
|
4
|
+
require_relative "../schemas/agent_schemas"
|
|
5
|
+
require_relative "../services/data_service"
|
|
6
|
+
|
|
7
|
+
module DhanHQ
|
|
8
|
+
module Agents
|
|
9
|
+
# Agent for market data retrieval decisions using LLM
|
|
10
|
+
class DataAgent < BaseAgent
|
|
11
|
+
def initialize(ollama_client:)
|
|
12
|
+
super(ollama_client: ollama_client, schema: DhanHQ::Schemas::AgentSchemas::DATA_AGENT_SCHEMA)
|
|
13
|
+
@data_service = DhanHQ::Services::DataService.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute_decision(decision)
|
|
17
|
+
action = decision["action"]
|
|
18
|
+
params = DhanHQ::Utils::ParameterNormalizer.normalize(decision["parameters"] || {})
|
|
19
|
+
|
|
20
|
+
@data_service.execute(action: action, params: params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def build_analysis_prompt(market_context:)
|
|
26
|
+
<<~PROMPT
|
|
27
|
+
Analyze the following market situation and decide the best data retrieval action:
|
|
28
|
+
|
|
29
|
+
Market Context:
|
|
30
|
+
#{market_context}
|
|
31
|
+
|
|
32
|
+
Available Actions (DATA ONLY - NO TRADING):
|
|
33
|
+
- get_market_quote: Get market quote using Instrument.quote convenience method (requires: symbol OR security_id as STRING, exchange_segment as STRING)
|
|
34
|
+
- get_live_ltp: Get live last traded price using Instrument.ltp convenience method (requires: symbol OR security_id as STRING, exchange_segment as STRING)
|
|
35
|
+
- get_market_depth: Get full market depth (bid/ask levels) using Instrument.quote convenience method (requires: symbol OR security_id as STRING, exchange_segment as STRING)
|
|
36
|
+
- get_historical_data: Get historical data using Instrument.daily/intraday convenience methods (requires: symbol OR security_id as STRING, exchange_segment as STRING, from_date, to_date, optional: interval, expiry_code)
|
|
37
|
+
- get_expired_options_data: Get expired options historical data (requires: symbol OR security_id as STRING, exchange_segment as STRING, expiry_date; optional: expiry_code, interval, instrument, expiry_flag, strike, drv_option_type, required_data)
|
|
38
|
+
- get_option_chain: Get option chain using Instrument.expiry_list/option_chain convenience methods (requires: symbol OR security_id as STRING, exchange_segment as STRING, optional: expiry)
|
|
39
|
+
- no_action: Take no action if unclear what data is needed
|
|
40
|
+
|
|
41
|
+
CRITICAL: Each API call handles ONLY ONE symbol at a time. If you need data for multiple symbols, choose ONE symbol for this decision.
|
|
42
|
+
- symbol must be a SINGLE STRING value (e.g., "NIFTY" or "RELIANCE"), NOT an array
|
|
43
|
+
- exchange_segment must be a SINGLE STRING value (e.g., "NSE_EQ" or "IDX_I"), NOT an array
|
|
44
|
+
- All APIs use Instrument.find() which expects SYMBOL (e.g., "NIFTY", "RELIANCE"), not security_id
|
|
45
|
+
- Instrument convenience methods automatically use the instrument's security_id, exchange_segment, and instrument attributes
|
|
46
|
+
- Use symbol when possible for better compatibility
|
|
47
|
+
Examples:
|
|
48
|
+
- For NIFTY: symbol="NIFTY", exchange_segment="IDX_I"
|
|
49
|
+
- For RELIANCE: symbol="RELIANCE", exchange_segment="NSE_EQ"
|
|
50
|
+
Valid exchange_segments: NSE_EQ, NSE_FNO, NSE_CURRENCY, BSE_EQ, BSE_FNO, BSE_CURRENCY, MCX_COMM, IDX_I
|
|
51
|
+
|
|
52
|
+
Decision Criteria:
|
|
53
|
+
- Only take actions with confidence > 0.6
|
|
54
|
+
- Focus on data retrieval, not trading decisions
|
|
55
|
+
- Provide all required parameters for the chosen action
|
|
56
|
+
|
|
57
|
+
Respond with a JSON object containing:
|
|
58
|
+
- action: one of the available actions
|
|
59
|
+
- reasoning: why this action was chosen
|
|
60
|
+
- confidence: your confidence level (0-1)
|
|
61
|
+
- parameters: object with required parameters for the action
|
|
62
|
+
PROMPT
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_agent"
|
|
4
|
+
require_relative "../schemas/agent_schemas"
|
|
5
|
+
|
|
6
|
+
module DhanHQ
|
|
7
|
+
module Agents
|
|
8
|
+
# Orchestrator agent that uses LLM to decide what to analyze
|
|
9
|
+
class OrchestratorAgent < BaseAgent
|
|
10
|
+
def initialize(ollama_client:)
|
|
11
|
+
super(ollama_client: ollama_client, schema: build_orchestration_schema)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def decide_analysis_plan(market_context: "", user_query: "")
|
|
15
|
+
prompt = build_orchestration_prompt(market_context: market_context, user_query: user_query)
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
plan = @ollama_client.generate(
|
|
19
|
+
prompt: prompt,
|
|
20
|
+
schema: build_orchestration_schema
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return { error: "Invalid plan" } unless plan.is_a?(Hash) && plan["analysis_plan"]
|
|
24
|
+
|
|
25
|
+
plan
|
|
26
|
+
rescue Ollama::Error => e
|
|
27
|
+
{ error: e.message }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
def build_analysis_prompt(market_context:)
|
|
34
|
+
# Not used, but required by base class
|
|
35
|
+
""
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_orchestration_prompt(market_context:, user_query:)
|
|
41
|
+
<<~PROMPT
|
|
42
|
+
You are a trading analysis orchestrator. Based on the market context and user query, decide what technical analysis to perform.
|
|
43
|
+
|
|
44
|
+
Market Context:
|
|
45
|
+
#{market_context.empty? ? 'No specific market context provided' : market_context}
|
|
46
|
+
|
|
47
|
+
User Query:
|
|
48
|
+
#{user_query.empty? ? 'No specific query - analyze current market opportunities' : user_query}
|
|
49
|
+
|
|
50
|
+
Your task is to create an analysis plan that includes:
|
|
51
|
+
1. Which symbols to analyze:
|
|
52
|
+
- Equity stocks: "RELIANCE", "TCS", "INFY", "HDFC" (use NSE_EQ or BSE_EQ)
|
|
53
|
+
- Indices: "NIFTY", "SENSEX", "BANKNIFTY" (use IDX_I)
|
|
54
|
+
2. Which exchange segments to use:
|
|
55
|
+
- NSE_EQ or BSE_EQ for equity stocks
|
|
56
|
+
- IDX_I for indices (NIFTY, SENSEX, BANKNIFTY)
|
|
57
|
+
3. What type of analysis to perform:
|
|
58
|
+
- "technical_analysis": Full technical analysis (trend, indicators, patterns) - can be used for both stocks and indices
|
|
59
|
+
- "swing_scan": Scan for swing trading opportunities - ONLY for equity stocks, NEVER for indices
|
|
60
|
+
- "options_scan": Scan for intraday options opportunities - ONLY for indices (NIFTY, SENSEX, BANKNIFTY)
|
|
61
|
+
- "all": Run all applicable analysis types (swing_scan only for stocks, options_scan only for indices)
|
|
62
|
+
4. Priority order (which symbols/analyses to run first)
|
|
63
|
+
|
|
64
|
+
Consider:
|
|
65
|
+
- If user mentions specific stocks, prioritize those
|
|
66
|
+
- If user asks about "swing trading", use swing_scan ONLY for equity stocks (not indices)
|
|
67
|
+
- If user asks about "options" or "intraday", use options_scan
|
|
68
|
+
- If no specific query, scan for opportunities across multiple symbols
|
|
69
|
+
- CRITICAL: Index symbols (NIFTY, SENSEX, BANKNIFTY) are INDICES, not stocks
|
|
70
|
+
* Indices should ONLY be analyzed with options_scan (for options buying opportunities)
|
|
71
|
+
* Indices should NEVER be analyzed with swing_scan (swing trading is for stocks only)
|
|
72
|
+
- For options, use index symbols (NIFTY, SENSEX, BANKNIFTY) with IDX_I segment
|
|
73
|
+
- For swing trading, use ONLY equity symbols (RELIANCE, TCS, INFY, HDFC) with NSE_EQ or BSE_EQ segment
|
|
74
|
+
|
|
75
|
+
Respond with a JSON object containing your analysis plan.
|
|
76
|
+
PROMPT
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build_orchestration_schema
|
|
80
|
+
{
|
|
81
|
+
"type" => "object",
|
|
82
|
+
"required" => ["analysis_plan", "reasoning"],
|
|
83
|
+
"properties" => {
|
|
84
|
+
"analysis_plan" => {
|
|
85
|
+
"type" => "array",
|
|
86
|
+
"items" => {
|
|
87
|
+
"type" => "object",
|
|
88
|
+
"required" => ["symbol", "exchange_segment", "analysis_type"],
|
|
89
|
+
"properties" => {
|
|
90
|
+
"symbol" => {
|
|
91
|
+
"type" => "string",
|
|
92
|
+
"description" => "Symbol to analyze (e.g., 'RELIANCE', 'NIFTY', 'TCS')"
|
|
93
|
+
},
|
|
94
|
+
"exchange_segment" => {
|
|
95
|
+
"type" => "string",
|
|
96
|
+
"enum" => ["NSE_EQ", "NSE_FNO", "NSE_CURRENCY", "BSE_EQ", "BSE_FNO",
|
|
97
|
+
"BSE_CURRENCY", "MCX_COMM", "IDX_I"],
|
|
98
|
+
"description" => "Exchange segment for the symbol"
|
|
99
|
+
},
|
|
100
|
+
"analysis_type" => {
|
|
101
|
+
"type" => "string",
|
|
102
|
+
"enum" => ["technical_analysis", "swing_scan", "options_scan", "all"],
|
|
103
|
+
"description" => "Type of analysis to perform"
|
|
104
|
+
},
|
|
105
|
+
"priority" => {
|
|
106
|
+
"type" => "number",
|
|
107
|
+
"description" => "Priority (1 = highest, lower numbers = higher priority)"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"reasoning" => {
|
|
113
|
+
"type" => "string",
|
|
114
|
+
"description" => "Why you chose this analysis plan"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|