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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/examples/advanced_multi_step_agent.rb +95 -18
  3. data/examples/dhanhq/README.md +236 -0
  4. data/examples/dhanhq/agents/base_agent.rb +76 -0
  5. data/examples/dhanhq/agents/data_agent.rb +66 -0
  6. data/examples/dhanhq/agents/orchestrator_agent.rb +121 -0
  7. data/examples/dhanhq/agents/technical_analysis_agent.rb +234 -0
  8. data/examples/dhanhq/agents/trading_agent.rb +81 -0
  9. data/examples/dhanhq/analysis/market_structure.rb +122 -0
  10. data/examples/dhanhq/analysis/pattern_recognizer.rb +175 -0
  11. data/examples/dhanhq/analysis/trend_analyzer.rb +90 -0
  12. data/examples/dhanhq/builders/market_context_builder.rb +67 -0
  13. data/examples/dhanhq/dhanhq_agent.rb +632 -0
  14. data/examples/dhanhq/indicators/technical_indicators.rb +160 -0
  15. data/examples/dhanhq/scanners/intraday_options_scanner.rb +387 -0
  16. data/examples/dhanhq/scanners/swing_scanner.rb +213 -0
  17. data/examples/dhanhq/schemas/agent_schemas.rb +61 -0
  18. data/examples/dhanhq/services/base_service.rb +46 -0
  19. data/examples/dhanhq/services/data_service.rb +120 -0
  20. data/examples/dhanhq/services/trading_service.rb +62 -0
  21. data/examples/dhanhq/technical_analysis_agentic_runner.rb +278 -0
  22. data/examples/dhanhq/technical_analysis_runner.rb +366 -0
  23. data/examples/dhanhq/utils/instrument_helper.rb +32 -0
  24. data/examples/dhanhq/utils/parameter_cleaner.rb +28 -0
  25. data/examples/dhanhq/utils/parameter_normalizer.rb +45 -0
  26. data/examples/dhanhq/utils/rate_limiter.rb +23 -0
  27. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +77 -0
  28. data/examples/dhanhq_agent.rb +220 -51
  29. data/examples/dhanhq_tools.rb +314 -121
  30. data/examples/tool_calling_pattern.rb +4 -1
  31. data/lib/ollama/version.rb +1 -1
  32. metadata +27 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f3a6bc948f8c0f17ccedf3f5f013c8d08d7315f189ef5dec237de4c74fbf257
4
- data.tar.gz: 3ce70c7e7af44fcc550860877ca4acb4d1c00af13f70c746b984d855be95aeef
3
+ metadata.gz: 6b1ed7c3f2d09174f4127c92b0b22eef90fc3deb895dc86183d296a6a70cc3b4
4
+ data.tar.gz: f2d0737f5cbb77557fa736fb45f7a5c7c94fd939d1005c53d68e82d229844283
5
5
  SHA512:
6
- metadata.gz: 7148b5b2dbe4dd8c5fb637b79837b1572283c1c2edaaa41df81fd75a347761809cdc7cbd9be8fd16d1cd99a33fbae25f716d436c0f1fde0304a6459338220335
7
- data.tar.gz: 78f06aa33cc1d08bc1b915fe9c374c8949c091ea946ac25116ee8d6ec8c9d8080ca74f5c43bdfed91401051ed775b3bc245f808483346d164ecc7033a41d8f92
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(_goal:)
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
- Workflow State:
163
- - Steps completed: #{context[:steps_completed].join(", ") || "none"}
164
- - Data collected: #{context[:data_collected].join(", ") || "none"}
165
- - Errors encountered: #{context[:error_count]}
166
-
167
- Analyze the current state and decide the next action.
168
- Consider:
169
- 1. What data still needs to be collected?
170
- 2. What analysis is needed?
171
- 3. What validation is required?
172
- 4. When should the workflow complete?
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"] || "unknown"
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"] || "data"
280
+ target = params["target"] || "collected_data"
208
281
  puts " 🔍 Analyzing: #{target}"
209
- { status: "analyzed", target: target, insights: "analysis_complete" }
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"] || "general"
292
+ validation_type = params["type"] || "results"
218
293
  puts " ✓ Validating: #{validation_type}"
219
- { status: "validated", type: validation_type }
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