ollama-client 0.2.5 → 0.2.7

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +336 -91
  4. data/RELEASE_NOTES_v0.2.6.md +41 -0
  5. data/docs/AREAS_FOR_CONSIDERATION.md +325 -0
  6. data/docs/EXAMPLE_REORGANIZATION.md +412 -0
  7. data/docs/FEATURES_ADDED.md +12 -1
  8. data/docs/GETTING_STARTED.md +361 -0
  9. data/docs/INTEGRATION_TESTING.md +170 -0
  10. data/docs/NEXT_STEPS_SUMMARY.md +114 -0
  11. data/docs/PERSONAS.md +383 -0
  12. data/docs/QUICK_START.md +195 -0
  13. data/docs/TESTING.md +392 -170
  14. data/docs/TEST_CHECKLIST.md +450 -0
  15. data/examples/README.md +62 -63
  16. data/examples/basic_chat.rb +33 -0
  17. data/examples/basic_generate.rb +29 -0
  18. data/examples/mcp_executor.rb +39 -0
  19. data/examples/mcp_http_executor.rb +45 -0
  20. data/examples/tool_calling_parsing.rb +59 -0
  21. data/examples/tool_dto_example.rb +0 -0
  22. data/exe/ollama-client +128 -1
  23. data/lib/ollama/agent/planner.rb +7 -2
  24. data/lib/ollama/chat_session.rb +101 -0
  25. data/lib/ollama/client.rb +41 -35
  26. data/lib/ollama/config.rb +9 -4
  27. data/lib/ollama/document_loader.rb +1 -1
  28. data/lib/ollama/embeddings.rb +61 -28
  29. data/lib/ollama/errors.rb +1 -0
  30. data/lib/ollama/mcp/http_client.rb +149 -0
  31. data/lib/ollama/mcp/stdio_client.rb +146 -0
  32. data/lib/ollama/mcp/tools_bridge.rb +72 -0
  33. data/lib/ollama/mcp.rb +31 -0
  34. data/lib/ollama/options.rb +3 -1
  35. data/lib/ollama/personas.rb +287 -0
  36. data/lib/ollama/version.rb +1 -1
  37. data/lib/ollama_client.rb +17 -5
  38. metadata +22 -48
  39. data/examples/advanced_complex_schemas.rb +0 -366
  40. data/examples/advanced_edge_cases.rb +0 -241
  41. data/examples/advanced_error_handling.rb +0 -200
  42. data/examples/advanced_multi_step_agent.rb +0 -341
  43. data/examples/advanced_performance_testing.rb +0 -186
  44. data/examples/chat_console.rb +0 -143
  45. data/examples/complete_workflow.rb +0 -245
  46. data/examples/dhan_console.rb +0 -843
  47. data/examples/dhanhq/README.md +0 -236
  48. data/examples/dhanhq/agents/base_agent.rb +0 -74
  49. data/examples/dhanhq/agents/data_agent.rb +0 -66
  50. data/examples/dhanhq/agents/orchestrator_agent.rb +0 -120
  51. data/examples/dhanhq/agents/technical_analysis_agent.rb +0 -252
  52. data/examples/dhanhq/agents/trading_agent.rb +0 -81
  53. data/examples/dhanhq/analysis/market_structure.rb +0 -138
  54. data/examples/dhanhq/analysis/pattern_recognizer.rb +0 -192
  55. data/examples/dhanhq/analysis/trend_analyzer.rb +0 -88
  56. data/examples/dhanhq/builders/market_context_builder.rb +0 -67
  57. data/examples/dhanhq/dhanhq_agent.rb +0 -829
  58. data/examples/dhanhq/indicators/technical_indicators.rb +0 -158
  59. data/examples/dhanhq/scanners/intraday_options_scanner.rb +0 -492
  60. data/examples/dhanhq/scanners/swing_scanner.rb +0 -247
  61. data/examples/dhanhq/schemas/agent_schemas.rb +0 -61
  62. data/examples/dhanhq/services/base_service.rb +0 -46
  63. data/examples/dhanhq/services/data_service.rb +0 -118
  64. data/examples/dhanhq/services/trading_service.rb +0 -59
  65. data/examples/dhanhq/technical_analysis_agentic_runner.rb +0 -411
  66. data/examples/dhanhq/technical_analysis_runner.rb +0 -420
  67. data/examples/dhanhq/test_tool_calling.rb +0 -538
  68. data/examples/dhanhq/test_tool_calling_verbose.rb +0 -251
  69. data/examples/dhanhq/utils/instrument_helper.rb +0 -32
  70. data/examples/dhanhq/utils/parameter_cleaner.rb +0 -28
  71. data/examples/dhanhq/utils/parameter_normalizer.rb +0 -45
  72. data/examples/dhanhq/utils/rate_limiter.rb +0 -23
  73. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +0 -72
  74. data/examples/dhanhq_agent.rb +0 -964
  75. data/examples/dhanhq_tools.rb +0 -1663
  76. data/examples/multi_step_agent_with_external_data.rb +0 -368
  77. data/examples/structured_outputs_chat.rb +0 -72
  78. data/examples/structured_tools.rb +0 -89
  79. data/examples/test_dhanhq_tool_calling.rb +0 -375
  80. data/examples/test_tool_calling.rb +0 -160
  81. data/examples/tool_calling_direct.rb +0 -124
  82. data/examples/tool_calling_pattern.rb +0 -269
  83. data/exe/dhan_console +0 -4
@@ -1,829 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # DhanHQ Agent - Complete trading agent with data retrieval and trading operations
5
- # Refactored with proper OOP structure, SOLID principles, and clean architecture
6
-
7
- require "json"
8
- require "date"
9
- require "dhan_hq"
10
- require_relative "../../lib/ollama_client"
11
- require_relative "../dhanhq_tools"
12
-
13
- # Load all modules in dependency order
14
- require_relative "utils/instrument_helper"
15
- require_relative "utils/rate_limiter"
16
- require_relative "utils/parameter_normalizer"
17
- require_relative "utils/parameter_cleaner"
18
- require_relative "utils/trading_parameter_normalizer"
19
- require_relative "builders/market_context_builder"
20
- require_relative "schemas/agent_schemas"
21
- require_relative "services/base_service"
22
- require_relative "services/data_service"
23
- require_relative "services/trading_service"
24
- require_relative "indicators/technical_indicators"
25
- require_relative "analysis/market_structure"
26
- require_relative "analysis/pattern_recognizer"
27
- require_relative "analysis/trend_analyzer"
28
- require_relative "agents/base_agent"
29
- require_relative "agents/data_agent"
30
- require_relative "agents/trading_agent"
31
- require_relative "agents/technical_analysis_agent"
32
- require_relative "agents/orchestrator_agent"
33
- require_relative "scanners/swing_scanner"
34
- require_relative "scanners/intraday_options_scanner"
35
-
36
- module DhanHQ
37
- # Main agent orchestrator
38
- class Agent
39
- def initialize(ollama_client: nil, trading_ollama_client: nil)
40
- @ollama_client = ollama_client || Ollama::Client.new
41
- @trading_ollama_client = trading_ollama_client || create_trading_client
42
- @data_agent = Agents::DataAgent.new(ollama_client: @ollama_client)
43
- @trading_agent = Agents::TradingAgent.new(ollama_client: @trading_ollama_client)
44
- @analysis_agent = Agents::TechnicalAnalysisAgent.new(ollama_client: @ollama_client)
45
- @orchestrator_agent = Agents::OrchestratorAgent.new(ollama_client: @ollama_client)
46
- @swing_scanner = Scanners::SwingScanner.new
47
- @options_scanner = Scanners::IntradayOptionsScanner.new
48
- end
49
-
50
- attr_reader :data_agent, :trading_agent, :analysis_agent, :orchestrator_agent, :swing_scanner, :options_scanner
51
-
52
- private
53
-
54
- def create_trading_client
55
- config = Ollama::Config.new
56
- config.timeout = 60
57
- Ollama::Client.new(config: config)
58
- end
59
- end
60
- end
61
-
62
- def price_range_stats(price_ranges)
63
- return nil unless price_ranges.is_a?(Array) && price_ranges.any?
64
-
65
- {
66
- min: price_ranges.min.round(2),
67
- max: price_ranges.max.round(2),
68
- avg: (price_ranges.sum / price_ranges.length).round(2),
69
- count: price_ranges.length
70
- }
71
- end
72
-
73
- def build_expired_options_summary(stats)
74
- {
75
- data_points: stats[:data_points] || 0,
76
- avg_volume: stats[:avg_volume]&.round(2),
77
- avg_open_interest: stats[:avg_open_interest]&.round(2),
78
- avg_implied_volatility: stats[:avg_implied_volatility]&.round(4),
79
- price_range_stats: price_range_stats(stats[:price_ranges]),
80
- has_ohlc: stats[:has_ohlc],
81
- has_volume: stats[:has_volume],
82
- has_open_interest: stats[:has_open_interest],
83
- has_implied_volatility: stats[:has_implied_volatility]
84
- }
85
- end
86
-
87
- def build_option_chain_summary(chain_result)
88
- chain = chain_result[:result][:chain]
89
- underlying_price = chain_result[:result][:underlying_last_price]
90
-
91
- unless chain.is_a?(Hash)
92
- return [{ expiry: chain_result[:result][:expiry], chain_type: chain.class },
93
- underlying_price]
94
- end
95
-
96
- strike_prices = chain.keys.sort_by(&:to_f)
97
- first_strike_data = strike_prices.any? ? chain[strike_prices.first] : nil
98
- atm_strike = select_atm_strike(strike_prices, underlying_price)
99
- atm_data = atm_strike ? chain[atm_strike] : nil
100
- sample_greeks = build_sample_greeks(atm_data, atm_strike)
101
-
102
- summary = {
103
- expiry: chain_result[:result][:expiry],
104
- underlying_last_price: underlying_price,
105
- strikes_count: strike_prices.length,
106
- has_call_options: option_type_present?(first_strike_data, "ce"),
107
- has_put_options: option_type_present?(first_strike_data, "pe"),
108
- has_greeks: sample_greeks.any?,
109
- strike_range: strike_range_summary(strike_prices),
110
- sample_greeks: sample_greeks.any? ? sample_greeks : nil
111
- }
112
-
113
- [summary, underlying_price]
114
- end
115
-
116
- def select_atm_strike(strike_prices, underlying_price)
117
- return strike_prices.first unless underlying_price && strike_prices.any?
118
-
119
- strike_prices.min_by { |strike| (strike.to_f - underlying_price).abs }
120
- end
121
-
122
- def option_type_present?(strike_data, key)
123
- strike_data.is_a?(Hash) && (strike_data.key?(key) || strike_data.key?(key.to_sym))
124
- end
125
-
126
- def strike_range_summary(strike_prices)
127
- return nil if strike_prices.empty?
128
-
129
- {
130
- min: strike_prices.first,
131
- max: strike_prices.last,
132
- sample_strikes: strike_prices.first(5)
133
- }
134
- end
135
-
136
- def build_sample_greeks(atm_data, atm_strike)
137
- return {} unless atm_data.is_a?(Hash)
138
-
139
- sample = {}
140
- call_data = atm_data["ce"] || atm_data[:ce]
141
- put_data = atm_data["pe"] || atm_data[:pe]
142
-
143
- call_greeks = extract_greeks(call_data)
144
- sample[:call] = greeks_summary(call_greeks, call_data, atm_strike) if call_greeks
145
-
146
- put_greeks = extract_greeks(put_data)
147
- sample[:put] = greeks_summary(put_greeks, put_data, atm_strike) if put_greeks
148
-
149
- sample
150
- end
151
-
152
- def extract_greeks(option_data)
153
- return nil unless option_data.is_a?(Hash)
154
- return nil unless option_data.key?("greeks") || option_data.key?(:greeks)
155
-
156
- option_data["greeks"] || option_data[:greeks]
157
- end
158
-
159
- def greeks_summary(greeks, option_data, atm_strike)
160
- {
161
- strike: atm_strike,
162
- delta: greeks["delta"] || greeks[:delta],
163
- theta: greeks["theta"] || greeks[:theta],
164
- gamma: greeks["gamma"] || greeks[:gamma],
165
- vega: greeks["vega"] || greeks[:vega],
166
- iv: option_data["implied_volatility"] || option_data[:implied_volatility],
167
- oi: option_data["oi"] || option_data[:oi],
168
- last_price: option_data["last_price"] || option_data[:last_price]
169
- }
170
- end
171
-
172
- def format_score_breakdown(details)
173
- "Trend=#{details[:trend]}, RSI=#{details[:rsi]}, MACD=#{details[:macd]}, " \
174
- "Structure=#{details[:structure]}, Patterns=#{details[:patterns]}"
175
- end
176
-
177
- def format_option_setup_details(setup)
178
- iv = setup[:iv]&.round(2) || "N/A"
179
- oi = setup[:oi] || "N/A"
180
- volume = setup[:volume] || "N/A"
181
- "IV: #{iv}% | OI: #{oi} | Volume: #{volume}"
182
- end
183
-
184
- def handle_option_chain_result(chain_result)
185
- if chain_result[:result] && chain_result[:result][:chain]
186
- chain_summary, underlying_price = build_option_chain_summary(chain_result)
187
- puts " ✅ Option chain retrieved for expiry: #{chain_result[:result][:expiry]}"
188
- puts " 📊 Underlying LTP: #{underlying_price}" if underlying_price
189
- puts " 📊 Chain summary: #{JSON.pretty_generate(chain_summary)}"
190
- elsif chain_result[:error]
191
- puts " ⚠️ Could not retrieve option chain data: #{chain_result[:error]}"
192
- end
193
- end
194
-
195
- # Main execution
196
- if __FILE__ == $PROGRAM_NAME
197
- # Configure DhanHQ
198
- begin
199
- DhanHQ.configure_with_env
200
- puts "✅ DhanHQ configured"
201
- rescue StandardError => e
202
- puts "⚠️ DhanHQ configuration error: #{e.message}"
203
- puts " Make sure CLIENT_ID and ACCESS_TOKEN are set in ENV"
204
- puts " Continuing with mock data for demonstration..."
205
- end
206
-
207
- puts "=" * 60
208
- puts "DhanHQ Agent: Ollama (Reasoning) + DhanHQ (Data & Trading)"
209
- puts "=" * 60
210
- puts
211
-
212
- # Initialize agent
213
- agent = DhanHQ::Agent.new
214
-
215
- # ============================================================
216
- # DATA AGENT EXAMPLES
217
- # ============================================================
218
- puts "─" * 60
219
- puts "DATA AGENT: Market Data Retrieval"
220
- puts "─" * 60
221
- puts
222
-
223
- # Example 1: Analyze market and decide data action (using real data)
224
- puts "Example 1: Market Analysis & Data Decision (Real Data)"
225
- puts "─" * 60
226
-
227
- # Fetch real market data first
228
- puts "📊 Fetching real market data from DhanHQ..."
229
-
230
- market_data = {}
231
- begin
232
- nifty_result = DhanHQDataTools.get_live_ltp(symbol: "NIFTY", exchange_segment: "IDX_I")
233
- sleep(1.2)
234
- if nifty_result.is_a?(Hash) && nifty_result[:result] && !nifty_result[:error]
235
- market_data[:nifty] = nifty_result[:result]
236
- ltp = nifty_result[:result][:ltp]
237
- if ltp && ltp != 0
238
- puts " ✅ NIFTY: LTP=#{ltp}"
239
- else
240
- puts " ⚠️ NIFTY: Data retrieved but LTP is null/empty (may be outside market hours)"
241
- puts " Result: #{JSON.pretty_generate(nifty_result[:result])}"
242
- end
243
- elsif nifty_result && nifty_result[:error]
244
- puts " ⚠️ NIFTY data error: #{nifty_result[:error]}"
245
- else
246
- puts " ⚠️ NIFTY: No data returned"
247
- end
248
- rescue StandardError => e
249
- puts " ⚠️ NIFTY data error: #{e.message}"
250
- end
251
-
252
- begin
253
- reliance_result = DhanHQDataTools.get_live_ltp(symbol: "RELIANCE", exchange_segment: "NSE_EQ")
254
- sleep(1.2)
255
- if reliance_result.is_a?(Hash) && reliance_result[:result] && !reliance_result[:error]
256
- market_data[:reliance] = reliance_result[:result]
257
- ltp = reliance_result[:result][:ltp]
258
- if ltp && ltp != 0
259
- puts " ✅ RELIANCE: LTP=#{ltp}"
260
- else
261
- puts " ⚠️ RELIANCE: Data retrieved but LTP is null/empty (may be outside market hours)"
262
- puts " Result: #{JSON.pretty_generate(reliance_result[:result])}"
263
- end
264
- elsif reliance_result && reliance_result[:error]
265
- puts " ⚠️ RELIANCE data error: #{reliance_result[:error]}"
266
- else
267
- puts " ⚠️ RELIANCE: No data returned"
268
- end
269
- rescue StandardError => e
270
- puts " ⚠️ RELIANCE data error: #{e.message}"
271
- end
272
-
273
- begin
274
- positions_result = { action: "check_positions", result: { positions: [], count: 0 },
275
- note: "Positions API not available in Data Tools" }
276
- if positions_result[:result]
277
- market_data[:positions] = positions_result[:result][:positions] || []
278
- puts " ✅ Positions: #{positions_result[:result][:count] || 0} active"
279
- else
280
- puts " ✅ Positions: 0 active (Positions API not in Data Tools)"
281
- market_data[:positions] = []
282
- end
283
- rescue StandardError => e
284
- puts " ⚠️ Positions error: #{e.message}"
285
- market_data[:positions] = []
286
- end
287
-
288
- puts
289
-
290
- # Build market context from real data
291
- market_context = DhanHQ::Builders::MarketContextBuilder.build(market_data)
292
-
293
- puts "Market Context (from real data):"
294
- puts market_context
295
- puts
296
-
297
- begin
298
- puts "🤔 Analyzing market with Ollama..."
299
- decision = agent.data_agent.analyze_and_decide(market_context: market_context)
300
-
301
- puts "\n📋 Decision:"
302
- if decision.is_a?(Hash)
303
- puts " Action: #{decision['action'] || 'N/A'}"
304
- puts " Reasoning: #{decision['reasoning'] || 'N/A'}"
305
- if decision["confidence"]
306
- puts " Confidence: #{(decision['confidence'] * 100).round}%"
307
- else
308
- puts " Confidence: N/A"
309
- end
310
- puts " Parameters: #{JSON.pretty_generate(decision['parameters'] || {})}"
311
- else
312
- puts " ⚠️ Invalid decision returned: #{decision.inspect}"
313
- end
314
-
315
- if decision["action"] != "no_action"
316
- puts "\n⚡ Executing data retrieval..."
317
- result = agent.data_agent.execute_decision(decision)
318
- puts " Result: #{JSON.pretty_generate(result)}"
319
- end
320
- rescue Ollama::Error => e
321
- puts "❌ Error: #{e.message}"
322
- end
323
-
324
- puts
325
- puts "─" * 60
326
- puts "Example 2: All Data APIs Demonstration"
327
- puts "─" * 60
328
- puts "Demonstrating all available DhanHQ Data APIs:"
329
- puts
330
-
331
- test_symbol = "RELIANCE"
332
- test_exchange = "NSE_EQ"
333
-
334
- # 1. Market Quote
335
- puts "1️⃣ Market Quote API"
336
- begin
337
- result = DhanHQDataTools.get_market_quote(symbol: test_symbol, exchange_segment: test_exchange)
338
- if result[:result]
339
- puts " ✅ Market Quote retrieved"
340
- puts " 📊 Quote data: #{JSON.pretty_generate(result[:result][:quote])}"
341
- else
342
- puts " ⚠️ #{result[:error]}"
343
- end
344
- rescue StandardError => e
345
- puts " ❌ Error: #{e.message}"
346
- end
347
-
348
- puts
349
- sleep(1.2)
350
-
351
- # 2. Live Market Feed (LTP)
352
- puts "2️⃣ Live Market Feed API (LTP)"
353
- begin
354
- result = DhanHQDataTools.get_live_ltp(symbol: test_symbol, exchange_segment: test_exchange)
355
- if result[:result]
356
- puts " ✅ LTP retrieved"
357
- puts " 📊 LTP: #{result[:result][:ltp].inspect}"
358
- else
359
- puts " ⚠️ #{result[:error]}"
360
- end
361
- rescue StandardError => e
362
- puts " ❌ Error: #{e.message}"
363
- end
364
-
365
- puts
366
- sleep(1.2)
367
-
368
- # 3. Full Market Depth
369
- puts "3️⃣ Full Market Depth API"
370
- begin
371
- result = DhanHQDataTools.get_market_depth(symbol: test_symbol, exchange_segment: test_exchange)
372
- if result[:result]
373
- puts " ✅ Market Depth retrieved"
374
- puts " 📊 Buy depth: #{result[:result][:buy_depth]&.length || 0} levels"
375
- puts " 📊 Sell depth: #{result[:result][:sell_depth]&.length || 0} levels"
376
- puts " 📊 LTP: #{result[:result][:ltp]}"
377
- puts " 📊 Volume: #{result[:result][:volume]}"
378
- else
379
- puts " ⚠️ #{result[:error]}"
380
- end
381
- rescue StandardError => e
382
- puts " ❌ Error: #{e.message}"
383
- end
384
-
385
- puts
386
- sleep(1.2)
387
-
388
- # 4. Historical Data
389
- puts "4️⃣ Historical Data API"
390
- begin
391
- to_date = Date.today.strftime("%Y-%m-%d")
392
- from_date = (Date.today - 30).strftime("%Y-%m-%d")
393
- result = DhanHQDataTools.get_historical_data(
394
- symbol: test_symbol,
395
- exchange_segment: test_exchange,
396
- from_date: from_date,
397
- to_date: to_date
398
- )
399
- if result[:result]
400
- puts " ✅ Historical data retrieved"
401
- puts " 📊 Type: #{result[:type]}"
402
- puts " 📊 Records: #{result[:result][:count]}"
403
- if result[:result][:count].zero?
404
- puts " ⚠️ No data found for date range #{from_date} to #{to_date}"
405
- puts " (This may be normal if market was closed or data unavailable)"
406
- end
407
- else
408
- puts " ⚠️ #{result[:error]}"
409
- end
410
- rescue StandardError => e
411
- puts " ❌ Error: #{e.message}"
412
- end
413
-
414
- puts
415
- sleep(0.5)
416
-
417
- # 5. Expired Options Data
418
- puts "5️⃣ Expired Options Data API"
419
- begin
420
- result = DhanHQDataTools.get_expired_options_data(
421
- security_id: "13",
422
- exchange_segment: "NSE_FNO",
423
- expiry_date: (Date.today - 7).strftime("%Y-%m-%d"),
424
- instrument: "OPTIDX",
425
- expiry_flag: "MONTH",
426
- expiry_code: 1,
427
- strike: "ATM",
428
- drv_option_type: "CALL",
429
- interval: "1"
430
- )
431
- if result[:result]
432
- puts " ✅ Expired options data retrieved"
433
- puts " 📊 Expiry: #{result[:result][:expiry_date]}"
434
- if result[:result][:summary_stats]
435
- stats = result[:result][:summary_stats]
436
- concise_summary = build_expired_options_summary(stats)
437
- puts " 📊 Data summary: #{JSON.pretty_generate(concise_summary)}"
438
- else
439
- puts " 📊 Data available but summary stats not found"
440
- end
441
- else
442
- puts " ⚠️ #{result[:error]}"
443
- puts " (Note: Options may require specific symbol format or may not exist for this instrument)"
444
- end
445
- rescue StandardError => e
446
- puts " ❌ Error: #{e.message}"
447
- end
448
-
449
- puts
450
- sleep(0.5)
451
-
452
- # 6. Option Chain
453
- puts "6️⃣ Option Chain API"
454
- begin
455
- expiry_list_result = DhanHQDataTools.get_option_chain(
456
- symbol: "NIFTY",
457
- exchange_segment: "IDX_I"
458
- )
459
- if expiry_list_result[:result] && expiry_list_result[:result][:expiries]
460
- expiries = expiry_list_result[:result][:expiries]
461
- puts " ✅ Available expiries: #{expiry_list_result[:result][:count]}"
462
- puts " 📊 First few expiries: #{expiries.first(3).inspect}" if expiries.is_a?(Array) && !expiries.empty?
463
-
464
- next_expiry = expiries.is_a?(Array) && !expiries.empty? ? expiries.first : nil
465
- if next_expiry
466
- puts " 📊 Fetching option chain for next expiry: #{next_expiry}"
467
- chain_result = DhanHQDataTools.get_option_chain(
468
- symbol: "NIFTY",
469
- exchange_segment: "IDX_I",
470
- expiry: next_expiry
471
- )
472
- handle_option_chain_result(chain_result)
473
- end
474
- elsif expiry_list_result[:error]
475
- puts " ⚠️ #{expiry_list_result[:error]}"
476
- puts " (Note: Options may require specific symbol format or may not exist for this instrument)"
477
- end
478
- rescue StandardError => e
479
- puts " ❌ Error: #{e.message}"
480
- end
481
-
482
- puts
483
- puts "=" * 60
484
- puts "TRADING AGENT: Order Parameter Building"
485
- puts "=" * 60
486
- puts
487
-
488
- # ============================================================
489
- # TRADING AGENT EXAMPLES
490
- # ============================================================
491
- puts "Example 1: Simple Buy Order"
492
- puts "─" * 60
493
-
494
- market_context = <<~CONTEXT
495
- RELIANCE is showing strong momentum.
496
- Current LTP: 2850
497
- Entry price: 2850
498
- Quantity: 100 shares
499
- Use regular order. security_id="1333", exchange_segment="NSE_EQ"
500
- CONTEXT
501
-
502
- puts "Market Context:"
503
- puts market_context
504
- puts
505
-
506
- begin
507
- puts "🤔 Analyzing with Ollama..."
508
- decision = agent.trading_agent.analyze_and_decide(market_context: market_context)
509
-
510
- puts "\n📋 Decision:"
511
- if decision.is_a?(Hash)
512
- puts " Action: #{decision['action'] || 'N/A'}"
513
- puts " Reasoning: #{decision['reasoning'] || 'N/A'}"
514
- puts " Confidence: #{(decision['confidence'] * 100).round}%" if decision["confidence"]
515
- puts " Parameters: #{JSON.pretty_generate(decision['parameters'] || {})}"
516
- end
517
-
518
- if decision["action"] != "no_action"
519
- puts "\n⚡ Building order parameters (order not placed)..."
520
- result = agent.trading_agent.execute_decision(decision)
521
- puts " Result: #{JSON.pretty_generate(result)}"
522
- if result.is_a?(Hash) && result[:order_params]
523
- puts "\n 📝 Order Parameters Ready:"
524
- puts " #{JSON.pretty_generate(result[:order_params])}"
525
- puts " 💡 To place order: DhanHQ::Models::Order.new(result[:order_params]).save"
526
- end
527
- end
528
- rescue Ollama::TimeoutError => e
529
- puts "⏱️ Timeout: #{e.message}"
530
- rescue Ollama::Error => e
531
- puts "❌ Error: #{e.message}"
532
- end
533
-
534
- puts
535
- puts "=" * 60
536
- puts "TECHNICAL ANALYSIS EXAMPLES"
537
- puts "=" * 60
538
- puts
539
-
540
- # ============================================================
541
- # TECHNICAL ANALYSIS EXAMPLES
542
- # ============================================================
543
- puts "Example 1: Technical Analysis for RELIANCE"
544
- puts "─" * 60
545
-
546
- begin
547
- analysis_result = agent.analysis_agent.analyze_symbol(
548
- symbol: "RELIANCE",
549
- exchange_segment: "NSE_EQ"
550
- )
551
-
552
- if analysis_result[:error]
553
- puts " ⚠️ Error: #{analysis_result[:error]}"
554
- elsif analysis_result[:analysis].nil? || analysis_result[:analysis].empty?
555
- puts " ⚠️ Error: Analysis returned empty result"
556
- else
557
- analysis = analysis_result[:analysis]
558
- puts " ✅ Analysis Complete"
559
- puts " 📊 Trend: #{analysis[:trend]&.dig(:trend) || 'N/A'} (#{analysis[:trend]&.dig(:strength) || 0}% strength)"
560
- puts " 📊 RSI: #{analysis[:indicators]&.dig(:rsi)&.round(2) || 'N/A'}"
561
- puts " 📊 MACD: #{analysis[:indicators]&.dig(:macd)&.round(2) || 'N/A'}"
562
- puts " 📊 Current Price: #{analysis[:current_price] || 'N/A'}"
563
- puts " 📊 Patterns Detected: #{analysis[:patterns]&.dig(:candlestick)&.length || 0} candlestick patterns"
564
- puts " 📊 Structure Break: #{analysis[:structure_break]&.dig(:broken) ? 'Yes' : 'No'}"
565
-
566
- # Generate swing trading recommendation
567
- begin
568
- recommendation = agent.analysis_agent.generate_recommendation(
569
- analysis_result,
570
- trading_style: :swing
571
- )
572
-
573
- if recommendation && !recommendation[:error] && recommendation.is_a?(Hash)
574
- puts "\n 💡 Swing Trading Recommendation:"
575
- puts " Action: #{recommendation['recommendation']&.upcase || 'N/A'}"
576
- puts " Entry: #{recommendation['entry_price'] || 'N/A'}"
577
- puts " Stop Loss: #{recommendation['stop_loss'] || 'N/A'}"
578
- puts " Target: #{recommendation['target_price'] || 'N/A'}"
579
- puts " Risk/Reward: #{recommendation['risk_reward_ratio']&.round(2) || 'N/A'}"
580
- puts " Confidence: #{(recommendation['confidence'] * 100).round}%" if recommendation["confidence"]
581
- end
582
- rescue StandardError => e
583
- puts " ⚠️ Could not generate recommendation: #{e.message}"
584
- end
585
- end
586
- rescue StandardError => e
587
- puts " ❌ Error: #{e.message}"
588
- end
589
-
590
- puts
591
- puts "Example 2: Swing Trading Scanner"
592
- puts "─" * 60
593
-
594
- begin
595
- # Scan a few symbols for swing opportunities
596
- symbols_to_scan = ["RELIANCE", "TCS", "INFY"]
597
- puts " 🔍 Scanning #{symbols_to_scan.length} symbols for swing opportunities..."
598
-
599
- candidates = agent.swing_scanner.scan_symbols(
600
- symbols_to_scan,
601
- exchange_segment: "NSE_EQ",
602
- min_score: 40,
603
- verbose: true
604
- )
605
-
606
- if candidates.empty?
607
- puts " ⚠️ No swing candidates found above minimum score (40/100)"
608
- puts " Try lowering min_score or check rejected candidates above"
609
- else
610
- puts " ✅ Found #{candidates.length} swing candidates:"
611
- candidates.each do |candidate|
612
- puts " 📈 #{candidate[:symbol]}: Score #{candidate[:score]}/100"
613
- if candidate[:score_details]
614
- details = candidate[:score_details]
615
- puts " Breakdown: #{format_score_breakdown(details)}"
616
- end
617
- trend = candidate[:analysis][:trend]
618
- puts " Trend: #{trend[:trend]} (#{trend[:strength]}% strength)"
619
- puts " #{candidate[:interpretation]}"
620
- end
621
- end
622
- rescue StandardError => e
623
- puts " ❌ Error: #{e.message}"
624
- puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
625
- end
626
-
627
- puts
628
- puts "Example 3: Intraday Options Scanner"
629
- puts "─" * 60
630
-
631
- begin
632
- puts " 🔍 Scanning NIFTY for intraday options opportunities..."
633
-
634
- options_setups = agent.options_scanner.scan_for_options_setups(
635
- "NIFTY",
636
- exchange_segment: "IDX_I",
637
- min_score: 40,
638
- verbose: true
639
- )
640
-
641
- if options_setups[:error]
642
- puts " ⚠️ #{options_setups[:error]}"
643
- elsif options_setups[:setups] && !options_setups[:setups].empty?
644
- puts " ✅ Found #{options_setups[:setups].length} options setups:"
645
- options_setups[:setups].each do |setup|
646
- puts " 📊 #{setup[:type].to_s.upcase} @ #{setup[:strike]}"
647
- puts " #{format_option_setup_details(setup)}"
648
- puts " Score: #{setup[:score]}/100 | Recommendation: #{setup[:recommendation]}"
649
- end
650
- else
651
- puts " ⚠️ No options setups found above minimum score (40/100)"
652
- puts " Check rejected setups above or try lowering min_score"
653
- end
654
- rescue StandardError => e
655
- puts " ❌ Error: #{e.message}"
656
- puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
657
- end
658
-
659
- puts
660
- puts "=" * 60
661
- puts "TOOL CALLING EXAMPLE (Using Executor + Structured Tool Classes)"
662
- puts "=" * 60
663
- puts
664
-
665
- # ============================================================
666
- # TOOL CALLING WITH EXECUTOR
667
- # ============================================================
668
- puts "Example: Agentic Tool Calling with DhanHQ Tools"
669
- puts "─" * 60
670
- puts "This demonstrates the new tool calling pattern using:"
671
- puts " - Structured Tool classes (type-safe schemas)"
672
- puts " - Executor (automatic tool execution loop)"
673
- puts " - chat_raw() internally (via Executor)"
674
- puts
675
-
676
- # Define DhanHQ tools using structured Tool classes
677
- market_quote_tool = Ollama::Tool.new(
678
- type: "function",
679
- function: Ollama::Tool::Function.new(
680
- name: "get_market_quote",
681
- description: "Get market quote for a symbol. Returns OHLC, depth, volume, and other market data. " \
682
- "Finds instrument automatically using exchange_segment and symbol.",
683
- parameters: Ollama::Tool::Function::Parameters.new(
684
- type: "object",
685
- properties: {
686
- symbol: Ollama::Tool::Function::Parameters::Property.new(
687
- type: "string",
688
- description: "Stock or index symbol (e.g., RELIANCE, NIFTY)"
689
- ),
690
- exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
691
- type: "string",
692
- description: "Exchange segment",
693
- enum: %w[NSE_EQ NSE_FNO BSE_EQ BSE_FNO IDX_I]
694
- )
695
- },
696
- required: %w[symbol exchange_segment]
697
- )
698
- )
699
- )
700
-
701
- live_ltp_tool = Ollama::Tool.new(
702
- type: "function",
703
- function: Ollama::Tool::Function.new(
704
- name: "get_live_ltp",
705
- description: "Get live last traded price (LTP) for a symbol. Fast API for current price. " \
706
- "Finds instrument automatically using exchange_segment and symbol.",
707
- parameters: Ollama::Tool::Function::Parameters.new(
708
- type: "object",
709
- properties: {
710
- symbol: Ollama::Tool::Function::Parameters::Property.new(
711
- type: "string",
712
- description: "Stock or index symbol"
713
- ),
714
- exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
715
- type: "string",
716
- description: "Exchange segment",
717
- enum: %w[NSE_EQ NSE_FNO BSE_EQ BSE_FNO IDX_I]
718
- )
719
- },
720
- required: %w[symbol exchange_segment]
721
- )
722
- )
723
- )
724
-
725
- # Define tools with structured Tool classes and callables
726
- tools = {
727
- "get_market_quote" => {
728
- tool: market_quote_tool,
729
- callable: lambda do |symbol:, exchange_segment:|
730
- result = DhanHQDataTools.get_market_quote(
731
- symbol: symbol.to_s,
732
- exchange_segment: exchange_segment.to_s
733
- )
734
-
735
- if result[:error]
736
- { error: result[:error] }
737
- else
738
- quote = result[:result][:quote]
739
- {
740
- symbol: symbol,
741
- exchange_segment: exchange_segment,
742
- last_price: quote[:last_price],
743
- volume: quote[:volume],
744
- ohlc: quote[:ohlc],
745
- change_percent: quote[:net_change]
746
- }
747
- end
748
- rescue StandardError => e
749
- { error: e.message }
750
- end
751
- },
752
-
753
- "get_live_ltp" => {
754
- tool: live_ltp_tool,
755
- callable: lambda do |symbol:, exchange_segment:|
756
- result = DhanHQDataTools.get_live_ltp(
757
- symbol: symbol.to_s,
758
- exchange_segment: exchange_segment.to_s
759
- )
760
-
761
- if result[:error]
762
- { error: result[:error] }
763
- else
764
- {
765
- symbol: symbol,
766
- exchange_segment: exchange_segment,
767
- ltp: result[:result][:ltp],
768
- timestamp: result[:result][:timestamp]
769
- }
770
- end
771
- rescue StandardError => e
772
- { error: e.message }
773
- end
774
- }
775
- }
776
-
777
- # Create executor with tools
778
- # Create client with same configuration as other examples
779
- executor_config = Ollama::Config.new
780
- executor_config.model = ENV.fetch("OLLAMA_MODEL", "llama3.1:8b")
781
- executor_config.temperature = 0.2
782
- executor_config.timeout = 60
783
- executor_client = Ollama::Client.new(config: executor_config)
784
-
785
- executor = Ollama::Agent::Executor.new(
786
- executor_client,
787
- tools: tools,
788
- max_steps: 10
789
- )
790
-
791
- begin
792
- puts "🔄 Starting agentic tool-calling loop..."
793
- puts "User query: Get market quote for RELIANCE and also check NIFTY's current price"
794
- puts
795
-
796
- result = executor.run(
797
- system: "You are a market data assistant. Use the available tools to get market data. " \
798
- "You can call multiple tools in sequence. When you have the data, summarize it for the user.",
799
- user: "Get market quote for RELIANCE stock and also check NIFTY's current price"
800
- )
801
-
802
- puts
803
- puts "=" * 60
804
- puts "Tool Calling Result:"
805
- puts "=" * 60
806
- puts result
807
- puts
808
- rescue Ollama::Error => e
809
- puts "❌ Error: #{e.message}"
810
- puts e.backtrace.first(5).join("\n") if e.backtrace
811
- rescue StandardError => e
812
- puts "❌ Unexpected error: #{e.message}"
813
- puts e.backtrace.first(3).join("\n") if e.backtrace
814
- end
815
-
816
- puts
817
- puts "=" * 60
818
- puts "DhanHQ Agent Summary:"
819
- puts " ✅ Ollama: Reasoning & Decision Making"
820
- puts " ✅ DhanHQ: Data Retrieval & Order Building"
821
- puts " ✅ Data APIs: Market Quote, Live Market Feed, Full Market Depth, " \
822
- "Historical Data, Expired Options Data, Option Chain"
823
- puts " ✅ Trading Tools: Order parameters, Super order parameters, Cancel parameters"
824
- puts " ✅ Technical Analysis: Trend analysis, SMC concepts, Pattern recognition, Indicators (RSI, MACD, MA, etc.)"
825
- puts " ✅ Scanners: Swing trading scanner, Intraday options scanner"
826
- puts " ✅ Analysis Agents: Technical analysis agent with LLM interpretation"
827
- puts " ✅ Tool Calling: Executor with structured Tool classes (NEW!)"
828
- puts "=" * 60
829
- end