ollama-client 0.2.0

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.
@@ -0,0 +1,752 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # DhanHQ Agent - Complete trading agent with data retrieval and trading operations
5
+ # This agent can:
6
+ # - Fetch and analyze market data (6 Data APIs)
7
+ # - Build order parameters for trading (does not place orders)
8
+
9
+ require "json"
10
+ require "dhan_hq"
11
+ require_relative "../lib/ollama_client"
12
+ require_relative "dhanhq_tools"
13
+
14
+ # Debug logging helper
15
+ def debug_log(location, message, data = {}, hypothesis_id = nil)
16
+ log_entry = {
17
+ sessionId: "debug-session",
18
+ runId: "run1",
19
+ hypothesisId: hypothesis_id,
20
+ location: location,
21
+ message: message,
22
+ data: data,
23
+ timestamp: Time.now.to_f * 1000
24
+ }
25
+ File.open("/home/nemesis/project/ollama-client/.cursor/debug.log", "a") do |f|
26
+ f.puts(log_entry.to_json)
27
+ end
28
+ rescue StandardError
29
+ # Ignore logging errors
30
+ end
31
+
32
+ # Helper to build market context from data
33
+ # rubocop:disable Metrics/PerceivedComplexity
34
+ def build_market_context_from_data(market_data)
35
+ context_parts = []
36
+
37
+ if market_data[:nifty]
38
+ ltp = market_data[:nifty][:ltp]
39
+ change = market_data[:nifty][:change_percent]
40
+ context_parts << if ltp && ltp != 0
41
+ "NIFTY is trading at #{ltp} (#{change || 'unknown'}% change)"
42
+ else
43
+ "NIFTY data retrieved but LTP is not available (may be outside market hours)"
44
+ end
45
+ else
46
+ context_parts << "NIFTY data not available"
47
+ end
48
+
49
+ if market_data[:reliance]
50
+ ltp = market_data[:reliance][:ltp]
51
+ change = market_data[:reliance][:change_percent]
52
+ volume = market_data[:reliance][:volume]
53
+ context_parts << if ltp && ltp != 0
54
+ "RELIANCE is at #{ltp} (#{change || 'unknown'}% change, Volume: #{volume || 'N/A'})"
55
+ else
56
+ "RELIANCE data retrieved but LTP is not available (may be outside market hours)"
57
+ end
58
+ else
59
+ context_parts << "RELIANCE data not available"
60
+ end
61
+
62
+ if market_data[:positions] && !market_data[:positions].empty?
63
+ context_parts << "Current positions: #{market_data[:positions].length} active"
64
+ market_data[:positions].each do |pos|
65
+ context_parts << " - #{pos[:trading_symbol]}: #{pos[:quantity]} @ #{pos[:average_price]}"
66
+ end
67
+ else
68
+ context_parts << "Current positions: None"
69
+ end
70
+
71
+ context_parts.join("\n")
72
+ end
73
+ # rubocop:enable Metrics/PerceivedComplexity
74
+
75
+ # Data-focused Agent using Ollama for reasoning
76
+ class DataAgent
77
+ def initialize(ollama_client:)
78
+ @ollama_client = ollama_client
79
+ @decision_schema = {
80
+ "type" => "object",
81
+ "required" => ["action", "reasoning", "confidence"],
82
+ "properties" => {
83
+ "action" => {
84
+ "type" => "string",
85
+ "enum" => ["get_market_quote", "get_live_ltp", "get_market_depth", "get_historical_data",
86
+ "get_expired_options_data", "get_option_chain", "no_action"]
87
+ },
88
+ "reasoning" => {
89
+ "type" => "string",
90
+ "description" => "Why this action was chosen"
91
+ },
92
+ "confidence" => {
93
+ "type" => "number",
94
+ "minimum" => 0,
95
+ "maximum" => 1,
96
+ "description" => "Confidence in this decision"
97
+ },
98
+ "parameters" => {
99
+ "type" => "object",
100
+ "description" => "Parameters for the action (symbol, exchange_segment, etc.)"
101
+ }
102
+ }
103
+ }
104
+ end
105
+
106
+ def analyze_and_decide(market_context:)
107
+ prompt = build_analysis_prompt(market_context: market_context)
108
+
109
+ begin
110
+ decision = @ollama_client.generate(
111
+ prompt: prompt,
112
+ schema: @decision_schema
113
+ )
114
+
115
+ # Validate confidence threshold
116
+ return { action: "no_action", reason: "invalid_decision" } unless decision.is_a?(Hash) && decision["confidence"]
117
+
118
+ if decision["confidence"] < 0.6
119
+ puts "āš ļø Low confidence (#{(decision["confidence"] * 100).round}%) - skipping action"
120
+ return { action: "no_action", reason: "low_confidence" }
121
+ end
122
+
123
+ decision
124
+ rescue Ollama::Error => e
125
+ puts "āŒ Ollama error: #{e.message}"
126
+ { action: "no_action", reason: "error", error: e.message }
127
+ rescue StandardError => e
128
+ puts "āŒ Unexpected error: #{e.message}"
129
+ { action: "no_action", reason: "error", error: e.message }
130
+ end
131
+ end
132
+
133
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
134
+ def execute_decision(decision)
135
+ action = decision["action"]
136
+ params = decision["parameters"] || {}
137
+
138
+ case action
139
+ when "get_market_quote"
140
+ if params["symbol"].nil? && (params["security_id"].nil? || params["security_id"].to_s.empty?)
141
+ { action: "get_market_quote", error: "Either symbol or security_id is required", params: params }
142
+ else
143
+ DhanHQDataTools.get_market_quote(
144
+ symbol: params["symbol"],
145
+ security_id: params["security_id"],
146
+ exchange_segment: params["exchange_segment"] || "NSE_EQ"
147
+ )
148
+ end
149
+
150
+ when "get_live_ltp"
151
+ if params["symbol"].nil? && (params["security_id"].nil? || params["security_id"].to_s.empty?)
152
+ { action: "get_live_ltp", error: "Either symbol or security_id is required", params: params }
153
+ else
154
+ DhanHQDataTools.get_live_ltp(
155
+ symbol: params["symbol"],
156
+ security_id: params["security_id"],
157
+ exchange_segment: params["exchange_segment"] || "NSE_EQ"
158
+ )
159
+ end
160
+
161
+ when "get_market_depth"
162
+ if params["symbol"].nil? && (params["security_id"].nil? || params["security_id"].to_s.empty?)
163
+ { action: "get_market_depth", error: "Either symbol or security_id is required", params: params }
164
+ else
165
+ DhanHQDataTools.get_market_depth(
166
+ symbol: params["symbol"],
167
+ security_id: params["security_id"],
168
+ exchange_segment: params["exchange_segment"] || "NSE_EQ"
169
+ )
170
+ end
171
+
172
+ when "get_historical_data"
173
+ if params["symbol"].nil? && (params["security_id"].nil? || params["security_id"].to_s.empty?)
174
+ { action: "get_historical_data", error: "Either symbol or security_id is required", params: params }
175
+ else
176
+ DhanHQDataTools.get_historical_data(
177
+ symbol: params["symbol"],
178
+ security_id: params["security_id"],
179
+ exchange_segment: params["exchange_segment"] || "NSE_EQ",
180
+ from_date: params["from_date"],
181
+ to_date: params["to_date"],
182
+ interval: params["interval"],
183
+ expiry_code: params["expiry_code"]
184
+ )
185
+ end
186
+
187
+ when "get_option_chain"
188
+ if params["symbol"].nil? && (params["security_id"].nil? || params["security_id"].to_s.empty?)
189
+ { action: "get_option_chain", error: "Either symbol or security_id is required", params: params }
190
+ else
191
+ DhanHQDataTools.get_option_chain(
192
+ symbol: params["symbol"],
193
+ security_id: params["security_id"],
194
+ exchange_segment: params["exchange_segment"] || "NSE_EQ",
195
+ expiry: params["expiry"]
196
+ )
197
+ end
198
+
199
+ when "get_expired_options_data"
200
+ symbol_or_id_missing = params["symbol"].nil? &&
201
+ (params["security_id"].nil? || params["security_id"].to_s.empty?)
202
+ if symbol_or_id_missing || params["expiry_date"].nil?
203
+ {
204
+ action: "get_expired_options_data",
205
+ error: "Either symbol or security_id, and expiry_date are required",
206
+ params: params
207
+ }
208
+ else
209
+ DhanHQDataTools.get_expired_options_data(
210
+ symbol: params["symbol"],
211
+ security_id: params["security_id"],
212
+ exchange_segment: params["exchange_segment"] || "NSE_FNO",
213
+ expiry_date: params["expiry_date"],
214
+ expiry_code: params["expiry_code"]
215
+ )
216
+ end
217
+
218
+ when "no_action"
219
+ { action: "no_action", message: "No action taken" }
220
+
221
+ else
222
+ { action: "unknown", error: "Unknown action: #{action}" }
223
+ end
224
+ end
225
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
226
+
227
+ private
228
+
229
+ def build_analysis_prompt(market_context:)
230
+ <<~PROMPT
231
+ Analyze the following market situation and decide the best data retrieval action:
232
+
233
+ Market Context:
234
+ #{market_context}
235
+
236
+ Available Actions (DATA ONLY - NO TRADING):
237
+ - get_market_quote: Get market quote using Instrument.quote convenience method (requires: symbol OR security_id, exchange_segment)
238
+ - get_live_ltp: Get live last traded price using Instrument.ltp convenience method (requires: symbol OR security_id, exchange_segment)
239
+ - get_market_depth: Get full market depth (bid/ask levels) using Instrument.quote convenience method (requires: symbol OR security_id, exchange_segment)
240
+ - get_historical_data: Get historical data using Instrument.daily/intraday convenience methods (requires: symbol OR security_id, exchange_segment, from_date, to_date, optional: interval, expiry_code)
241
+ - get_expired_options_data: Get expired options historical data using Instrument.daily convenience method (requires: symbol OR security_id, exchange_segment, expiry_date, optional: expiry_code)
242
+ - get_option_chain: Get option chain using Instrument.expiry_list/option_chain convenience methods (requires: symbol OR security_id, exchange_segment, optional: expiry)
243
+ - no_action: Take no action if unclear what data is needed
244
+
245
+ Important:
246
+ - All APIs use Instrument.find() which expects SYMBOL (e.g., "NIFTY", "RELIANCE"), not security_id
247
+ - Instrument convenience methods automatically use the instrument's security_id, exchange_segment, and instrument attributes
248
+ - Use symbol when possible for better compatibility
249
+ Examples: NIFTY=symbol "NIFTY", exchange_segment "IDX_I"; RELIANCE=symbol "RELIANCE", exchange_segment "NSE_EQ"
250
+ Valid exchange_segments: NSE_EQ, NSE_FNO, NSE_CURRENCY, BSE_EQ, BSE_FNO, BSE_CURRENCY, MCX_COMM, IDX_I
251
+
252
+ Decision Criteria:
253
+ - Only take actions with confidence > 0.6
254
+ - Focus on data retrieval, not trading decisions
255
+ - Provide all required parameters for the chosen action
256
+
257
+ Respond with a JSON object containing:
258
+ - action: one of the available actions
259
+ - reasoning: why this action was chosen
260
+ - confidence: your confidence level (0-1)
261
+ - parameters: object with required parameters for the action
262
+ PROMPT
263
+ end
264
+ end
265
+
266
+ # Trading-focused Agent using Ollama for reasoning
267
+ class TradingAgent
268
+ def initialize(ollama_client:)
269
+ @ollama_client = ollama_client
270
+ @decision_schema = {
271
+ "type" => "object",
272
+ "required" => ["action", "reasoning", "confidence"],
273
+ "properties" => {
274
+ "action" => {
275
+ "type" => "string",
276
+ "enum" => ["place_order", "place_super_order", "cancel_order", "no_action"]
277
+ },
278
+ "reasoning" => {
279
+ "type" => "string",
280
+ "description" => "Why this action was chosen"
281
+ },
282
+ "confidence" => {
283
+ "type" => "number",
284
+ "minimum" => 0,
285
+ "maximum" => 1,
286
+ "description" => "Confidence in this decision"
287
+ },
288
+ "parameters" => {
289
+ "type" => "object",
290
+ "description" => "Parameters for the action (security_id, quantity, price, etc.)"
291
+ }
292
+ }
293
+ }
294
+ end
295
+
296
+ def analyze_and_decide(market_context:)
297
+ prompt = build_analysis_prompt(market_context: market_context)
298
+
299
+ begin
300
+ decision = @ollama_client.generate(
301
+ prompt: prompt,
302
+ schema: @decision_schema
303
+ )
304
+
305
+ # Validate confidence threshold
306
+ return { action: "no_action", reason: "invalid_decision" } unless decision.is_a?(Hash) && decision["confidence"]
307
+
308
+ if decision["confidence"] < 0.6
309
+ puts "āš ļø Low confidence (#{(decision["confidence"] * 100).round}%) - skipping action"
310
+ return { action: "no_action", reason: "low_confidence" }
311
+ end
312
+
313
+ decision
314
+ rescue Ollama::Error => e
315
+ puts "āŒ Ollama error: #{e.message}"
316
+ { action: "no_action", reason: "error", error: e.message }
317
+ rescue StandardError => e
318
+ puts "āŒ Unexpected error: #{e.message}"
319
+ { action: "no_action", reason: "error", error: e.message }
320
+ end
321
+ end
322
+
323
+ def execute_decision(decision)
324
+ action = decision["action"]
325
+ params = decision["parameters"] || {}
326
+
327
+ case action
328
+ when "place_order"
329
+ handle_place_order(params)
330
+ when "place_super_order"
331
+ handle_place_super_order(params)
332
+ when "cancel_order"
333
+ handle_cancel_order(params)
334
+ when "no_action"
335
+ handle_no_action
336
+ else
337
+ handle_unknown_action(action)
338
+ end
339
+ end
340
+
341
+ private
342
+
343
+ def handle_place_order(params)
344
+ DhanHQTradingTools.build_order_params(
345
+ transaction_type: params["transaction_type"] || "BUY",
346
+ exchange_segment: params["exchange_segment"] || "NSE_EQ",
347
+ product_type: params["product_type"] || "MARGIN",
348
+ order_type: params["order_type"] || "LIMIT",
349
+ security_id: params["security_id"],
350
+ quantity: params["quantity"] || 1,
351
+ price: params["price"]
352
+ )
353
+ end
354
+
355
+ def handle_place_super_order(params)
356
+ DhanHQTradingTools.build_super_order_params(
357
+ transaction_type: params["transaction_type"] || "BUY",
358
+ exchange_segment: params["exchange_segment"] || "NSE_EQ",
359
+ product_type: params["product_type"] || "MARGIN",
360
+ order_type: params["order_type"] || "LIMIT",
361
+ security_id: params["security_id"],
362
+ quantity: params["quantity"] || 1,
363
+ price: params["price"],
364
+ target_price: params["target_price"],
365
+ stop_loss_price: params["stop_loss_price"],
366
+ trailing_jump: params["trailing_jump"] || 10
367
+ )
368
+ end
369
+
370
+ def handle_cancel_order(params)
371
+ DhanHQTradingTools.build_cancel_params(order_id: params["order_id"])
372
+ end
373
+
374
+ def handle_no_action
375
+ { action: "no_action", message: "No action taken" }
376
+ end
377
+
378
+ def handle_unknown_action(action)
379
+ { action: "unknown", error: "Unknown action: #{action}" }
380
+ end
381
+
382
+ def build_analysis_prompt(market_context:)
383
+ <<~PROMPT
384
+ Analyze the following market situation and decide the best trading action:
385
+
386
+ Market Context:
387
+ #{market_context}
388
+
389
+ Available Actions (TRADING ONLY):
390
+ - place_order: Build order parameters (requires: security_id as string, quantity, price, transaction_type, exchange_segment)
391
+ - place_super_order: Build super order parameters with SL/TP (requires: security_id as string, quantity, price, target_price, stop_loss_price, exchange_segment)
392
+ - cancel_order: Build cancel parameters (requires: order_id)
393
+ - no_action: Take no action if market conditions are unclear or risky
394
+
395
+ Important: security_id must be a STRING (e.g., "13" not 13). Valid exchange_segment values: NSE_EQ, NSE_FNO, NSE_CURRENCY, BSE_EQ, BSE_FNO, BSE_CURRENCY, MCX_COMM, IDX_I
396
+
397
+ Decision Criteria:
398
+ - Only take actions with confidence > 0.6
399
+ - Consider risk management (use super orders for risky trades)
400
+ - Ensure all required parameters are provided
401
+ - Be conservative - prefer no_action if uncertain
402
+
403
+ Respond with a JSON object containing:
404
+ - action: one of the available trading actions
405
+ - reasoning: why this action was chosen
406
+ - confidence: your confidence level (0-1)
407
+ - parameters: object with required parameters for the action
408
+ PROMPT
409
+ end
410
+ end
411
+
412
+ # Main execution
413
+ if __FILE__ == $PROGRAM_NAME
414
+ # Configure DhanHQ (must be done before using DhanHQ models)
415
+ begin
416
+ DhanHQ.configure_with_env
417
+ puts "āœ… DhanHQ configured"
418
+ rescue StandardError => e
419
+ puts "āš ļø DhanHQ configuration error: #{e.message}"
420
+ puts " Make sure CLIENT_ID and ACCESS_TOKEN are set in ENV"
421
+ puts " Continuing with mock data for demonstration..."
422
+ end
423
+
424
+ puts "=" * 60
425
+ puts "DhanHQ Agent: Ollama (Reasoning) + DhanHQ (Data & Trading)"
426
+ puts "=" * 60
427
+ puts
428
+
429
+ # Initialize Ollama client
430
+ ollama_client = Ollama::Client.new
431
+
432
+ # ============================================================
433
+ # DATA AGENT EXAMPLES
434
+ # ============================================================
435
+ puts "─" * 60
436
+ puts "DATA AGENT: Market Data Retrieval"
437
+ puts "─" * 60
438
+ puts
439
+
440
+ data_agent = DataAgent.new(ollama_client: ollama_client)
441
+
442
+ # Example 1: Analyze market and decide data action (using real data)
443
+ puts "Example 1: Market Analysis & Data Decision (Real Data)"
444
+ puts "─" * 60
445
+
446
+ # Fetch real market data first
447
+ puts "šŸ“Š Fetching real market data from DhanHQ..."
448
+
449
+ market_data = {}
450
+ begin
451
+ # Get NIFTY data - using Instrument convenience method (uses symbol)
452
+ # Note: Instrument.find expects symbol "NIFTY", not security_id
453
+ # Rate limiting is handled automatically in DhanHQDataTools
454
+ nifty_result = DhanHQDataTools.get_live_ltp(symbol: "NIFTY", exchange_segment: "IDX_I")
455
+ sleep(1.2) # Rate limit: 1 request per second for MarketFeed APIs
456
+ if nifty_result.is_a?(Hash) && nifty_result[:result] && !nifty_result[:error]
457
+ market_data[:nifty] = nifty_result[:result]
458
+ ltp = nifty_result[:result][:ltp]
459
+ if ltp && ltp != 0
460
+ puts " āœ… NIFTY: LTP=#{ltp}"
461
+ else
462
+ puts " āš ļø NIFTY: Data retrieved but LTP is null/empty (may be outside market hours)"
463
+ puts " Result: #{nifty_result[:result].inspect[0..200]}"
464
+ end
465
+ elsif nifty_result && nifty_result[:error]
466
+ puts " āš ļø NIFTY data error: #{nifty_result[:error]}"
467
+ else
468
+ puts " āš ļø NIFTY: No data returned"
469
+ end
470
+ rescue StandardError => e
471
+ puts " āš ļø NIFTY data error: #{e.message}"
472
+ end
473
+
474
+ begin
475
+ # Get RELIANCE data - using Instrument convenience method (uses symbol)
476
+ # Note: Instrument.find expects symbol "RELIANCE", not security_id
477
+ # Rate limiting is handled automatically in DhanHQDataTools
478
+ reliance_result = DhanHQDataTools.get_live_ltp(symbol: "RELIANCE", exchange_segment: "NSE_EQ")
479
+ sleep(1.2) # Rate limit: 1 request per second for MarketFeed APIs
480
+ if reliance_result.is_a?(Hash) && reliance_result[:result] && !reliance_result[:error]
481
+ market_data[:reliance] = reliance_result[:result]
482
+ ltp = reliance_result[:result][:ltp]
483
+ if ltp && ltp != 0
484
+ puts " āœ… RELIANCE: LTP=#{ltp}"
485
+ else
486
+ puts " āš ļø RELIANCE: Data retrieved but LTP is null/empty (may be outside market hours)"
487
+ puts " Result: #{reliance_result[:result].inspect[0..200]}"
488
+ end
489
+ elsif reliance_result && reliance_result[:error]
490
+ puts " āš ļø RELIANCE data error: #{reliance_result[:error]}"
491
+ else
492
+ puts " āš ļø RELIANCE: No data returned"
493
+ end
494
+ rescue StandardError => e
495
+ puts " āš ļø RELIANCE data error: #{e.message}"
496
+ end
497
+
498
+ # NOTE: Positions and holdings are not part of the 6 Data APIs
499
+ begin
500
+ positions_result = { action: "check_positions", result: { positions: [], count: 0 },
501
+ note: "Positions API not available in Data Tools" }
502
+ if positions_result[:result]
503
+ market_data[:positions] = positions_result[:result][:positions] || []
504
+ puts " āœ… Positions: #{positions_result[:result][:count] || 0} active"
505
+ else
506
+ puts " āœ… Positions: 0 active (Positions API not in Data Tools)"
507
+ market_data[:positions] = []
508
+ end
509
+ rescue StandardError => e
510
+ puts " āš ļø Positions error: #{e.message}"
511
+ market_data[:positions] = []
512
+ end
513
+
514
+ puts
515
+
516
+ # Build market context from real data
517
+ market_context = build_market_context_from_data(market_data)
518
+
519
+ puts "Market Context (from real data):"
520
+ puts market_context
521
+ puts
522
+
523
+ begin
524
+ puts "šŸ¤” Analyzing market with Ollama..."
525
+ decision = data_agent.analyze_and_decide(market_context: market_context)
526
+
527
+ puts "\nšŸ“‹ Decision:"
528
+ if decision.is_a?(Hash)
529
+ puts " Action: #{decision['action'] || 'N/A'}"
530
+ puts " Reasoning: #{decision['reasoning'] || 'N/A'}"
531
+ if decision["confidence"]
532
+ puts " Confidence: #{(decision['confidence'] * 100).round}%"
533
+ else
534
+ puts " Confidence: N/A"
535
+ end
536
+ puts " Parameters: #{JSON.pretty_generate(decision['parameters'] || {})}"
537
+ else
538
+ puts " āš ļø Invalid decision returned: #{decision.inspect}"
539
+ end
540
+
541
+ if decision["action"] != "no_action"
542
+ puts "\n⚔ Executing data retrieval..."
543
+ result = data_agent.execute_decision(decision)
544
+ puts " Result: #{JSON.pretty_generate(result)}"
545
+ end
546
+ rescue Ollama::Error => e
547
+ puts "āŒ Error: #{e.message}"
548
+ end
549
+
550
+ puts
551
+ puts "─" * 60
552
+ puts "Example 2: All Data APIs Demonstration"
553
+ puts "─" * 60
554
+ puts "Demonstrating all available DhanHQ Data APIs:"
555
+ puts
556
+
557
+ test_symbol = "RELIANCE" # RELIANCE symbol for Instrument.find
558
+ test_exchange = "NSE_EQ"
559
+
560
+ # 1. Market Quote (uses Instrument convenience method)
561
+ puts "1ļøāƒ£ Market Quote API"
562
+ begin
563
+ result = DhanHQDataTools.get_market_quote(symbol: test_symbol, exchange_segment: test_exchange)
564
+ if result[:result]
565
+ puts " āœ… Market Quote retrieved"
566
+ puts " šŸ“Š Quote data: #{result[:result][:quote].inspect[0..150]}"
567
+ else
568
+ puts " āš ļø #{result[:error]}"
569
+ end
570
+ rescue StandardError => e
571
+ puts " āŒ Error: #{e.message}"
572
+ end
573
+
574
+ puts
575
+ sleep(1.2) # Rate limit: 1 request per second for MarketFeed APIs
576
+
577
+ # 2. Live Market Feed (LTP) (uses Instrument convenience method)
578
+ puts "2ļøāƒ£ Live Market Feed API (LTP)"
579
+ begin
580
+ result = DhanHQDataTools.get_live_ltp(symbol: test_symbol, exchange_segment: test_exchange)
581
+ if result[:result]
582
+ puts " āœ… LTP retrieved"
583
+ puts " šŸ“Š LTP: #{result[:result][:ltp].inspect}"
584
+ else
585
+ puts " āš ļø #{result[:error]}"
586
+ end
587
+ rescue StandardError => e
588
+ puts " āŒ Error: #{e.message}"
589
+ end
590
+
591
+ puts
592
+ sleep(1.2) # Rate limit: 1 request per second for MarketFeed APIs
593
+
594
+ # 3. Full Market Depth (uses Instrument convenience method)
595
+ puts "3ļøāƒ£ Full Market Depth API"
596
+ begin
597
+ result = DhanHQDataTools.get_market_depth(symbol: test_symbol, exchange_segment: test_exchange)
598
+ if result[:result]
599
+ puts " āœ… Market Depth retrieved"
600
+ puts " šŸ“Š Buy depth: #{result[:result][:buy_depth]&.length || 0} levels"
601
+ puts " šŸ“Š Sell depth: #{result[:result][:sell_depth]&.length || 0} levels"
602
+ puts " šŸ“Š LTP: #{result[:result][:ltp]}"
603
+ puts " šŸ“Š Volume: #{result[:result][:volume]}"
604
+ else
605
+ puts " āš ļø #{result[:error]}"
606
+ end
607
+ rescue StandardError => e
608
+ puts " āŒ Error: #{e.message}"
609
+ end
610
+
611
+ puts
612
+ sleep(1.2) # Rate limit: 1 request per second for MarketFeed APIs
613
+
614
+ # 4. Historical Data (uses symbol for Instrument.find)
615
+ puts "4ļøāƒ£ Historical Data API"
616
+ begin
617
+ result = DhanHQDataTools.get_historical_data(
618
+ symbol: test_symbol,
619
+ exchange_segment: test_exchange,
620
+ from_date: "2024-01-01",
621
+ to_date: "2024-01-31"
622
+ )
623
+ if result[:result]
624
+ puts " āœ… Historical data retrieved"
625
+ puts " šŸ“Š Type: #{result[:type]}"
626
+ puts " šŸ“Š Records: #{result[:result][:count]}"
627
+ else
628
+ puts " āš ļø #{result[:error]}"
629
+ end
630
+ rescue StandardError => e
631
+ puts " āŒ Error: #{e.message}"
632
+ end
633
+
634
+ puts
635
+ sleep(0.5) # Small delay for Instrument APIs
636
+
637
+ # 5. Expired Options Data (uses symbol for Instrument.find)
638
+ puts "5ļøāƒ£ Expired Options Data API"
639
+ begin
640
+ # Use NSE_FNO for options
641
+ result = DhanHQDataTools.get_expired_options_data(
642
+ symbol: test_symbol,
643
+ exchange_segment: "NSE_FNO",
644
+ expiry_date: "2024-01-31"
645
+ )
646
+ if result[:result]
647
+ puts " āœ… Expired options data retrieved"
648
+ puts " šŸ“Š Expiry: #{result[:result][:expiry_date]}"
649
+ puts " šŸ“Š Data: #{result[:result][:data].inspect[0..150]}"
650
+ else
651
+ puts " āš ļø #{result[:error]}"
652
+ end
653
+ rescue StandardError => e
654
+ puts " āŒ Error: #{e.message}"
655
+ end
656
+
657
+ puts
658
+ sleep(0.5) # Small delay for Instrument APIs
659
+
660
+ # 6. Option Chain (uses symbol for Instrument.find)
661
+ puts "6ļøāƒ£ Option Chain API"
662
+ begin
663
+ result = DhanHQDataTools.get_option_chain(
664
+ symbol: test_symbol,
665
+ exchange_segment: "NSE_FNO"
666
+ )
667
+ if result[:result]
668
+ puts " āœ… Option chain data retrieved"
669
+ if result[:result][:expiries]
670
+ puts " šŸ“Š Available expiries: #{result[:result][:count]}"
671
+ expiries = result[:result][:expiries]
672
+ puts " šŸ“Š First few expiries: #{expiries.first(3).inspect}" if expiries.is_a?(Array)
673
+ elsif result[:result][:chain]
674
+ puts " šŸ“Š Option chain: #{result[:result][:chain].inspect[0..150]}"
675
+ end
676
+ else
677
+ puts " āš ļø #{result[:error]}"
678
+ end
679
+ rescue StandardError => e
680
+ puts " āŒ Error: #{e.message}"
681
+ end
682
+
683
+ puts
684
+ puts "=" * 60
685
+ puts "TRADING AGENT: Order Parameter Building"
686
+ puts "=" * 60
687
+ puts
688
+
689
+ # ============================================================
690
+ # TRADING AGENT EXAMPLES
691
+ # ============================================================
692
+ config = Ollama::Config.new
693
+ config.timeout = 60
694
+ trading_ollama_client = Ollama::Client.new(config: config)
695
+ trading_agent = TradingAgent.new(ollama_client: trading_ollama_client)
696
+
697
+ # Example 1: Simple buy order
698
+ puts "Example 1: Simple Buy Order"
699
+ puts "─" * 60
700
+
701
+ market_context = <<~CONTEXT
702
+ RELIANCE is showing strong momentum.
703
+ Current LTP: 2,850
704
+ Entry price: 2,850
705
+ Quantity: 100 shares
706
+ Use regular order. security_id="1333", exchange_segment="NSE_EQ"
707
+ CONTEXT
708
+
709
+ puts "Market Context:"
710
+ puts market_context
711
+ puts
712
+
713
+ begin
714
+ puts "šŸ¤” Analyzing with Ollama..."
715
+ decision = trading_agent.analyze_and_decide(market_context: market_context)
716
+
717
+ puts "\nšŸ“‹ Decision:"
718
+ if decision.is_a?(Hash)
719
+ puts " Action: #{decision['action'] || 'N/A'}"
720
+ puts " Reasoning: #{decision['reasoning'] || 'N/A'}"
721
+ puts " Confidence: #{(decision['confidence'] * 100).round}%" if decision["confidence"]
722
+ puts " Parameters: #{JSON.pretty_generate(decision['parameters'] || {})}"
723
+ end
724
+
725
+ if decision["action"] != "no_action"
726
+ puts "\n⚔ Building order parameters (order not placed)..."
727
+ result = trading_agent.execute_decision(decision)
728
+ puts " Result: #{JSON.pretty_generate(result)}"
729
+ if result.is_a?(Hash) && result[:order_params]
730
+ puts "\n šŸ“ Order Parameters Ready:"
731
+ puts " #{JSON.pretty_generate(result[:order_params])}"
732
+ puts " šŸ’” To place order: DhanHQ::Models::Order.new(result[:order_params]).save"
733
+ end
734
+ end
735
+ rescue Ollama::TimeoutError => e
736
+ puts "ā±ļø Timeout: #{e.message}"
737
+ rescue Ollama::Error => e
738
+ puts "āŒ Error: #{e.message}"
739
+ end
740
+
741
+ puts
742
+ puts "=" * 60
743
+ puts "DhanHQ Agent Summary:"
744
+ puts " āœ… Ollama: Reasoning & Decision Making"
745
+ puts " āœ… DhanHQ: Data Retrieval & Order Building"
746
+ puts " āœ… Data APIs: Market Quote, Live Market Feed, Full Market Depth, " \
747
+ "Historical Data, Expired Options Data, Option Chain"
748
+ puts " āœ… Trading Tools: Order parameters, Super order parameters, Cancel parameters"
749
+ puts " āœ… Instrument Convenience Methods: ltp, ohlc, quote, daily, intraday, expiry_list, option_chain"
750
+ puts "=" * 60
751
+ end
752
+