ollama-client 0.2.1 → 0.2.3

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +220 -12
  4. data/docs/CLOUD.md +29 -0
  5. data/docs/CONSOLE_IMPROVEMENTS.md +256 -0
  6. data/docs/FEATURES_ADDED.md +145 -0
  7. data/docs/HANDLERS_ANALYSIS.md +190 -0
  8. data/docs/README.md +37 -0
  9. data/docs/SCHEMA_FIXES.md +147 -0
  10. data/docs/TEST_UPDATES.md +107 -0
  11. data/examples/README.md +92 -0
  12. data/examples/advanced_complex_schemas.rb +6 -3
  13. data/examples/advanced_multi_step_agent.rb +13 -7
  14. data/examples/chat_console.rb +143 -0
  15. data/examples/complete_workflow.rb +14 -4
  16. data/examples/dhan_console.rb +843 -0
  17. data/examples/dhanhq/agents/base_agent.rb +0 -2
  18. data/examples/dhanhq/agents/orchestrator_agent.rb +1 -2
  19. data/examples/dhanhq/agents/technical_analysis_agent.rb +67 -49
  20. data/examples/dhanhq/analysis/market_structure.rb +44 -28
  21. data/examples/dhanhq/analysis/pattern_recognizer.rb +64 -47
  22. data/examples/dhanhq/analysis/trend_analyzer.rb +6 -8
  23. data/examples/dhanhq/dhanhq_agent.rb +296 -99
  24. data/examples/dhanhq/indicators/technical_indicators.rb +3 -5
  25. data/examples/dhanhq/scanners/intraday_options_scanner.rb +360 -255
  26. data/examples/dhanhq/scanners/swing_scanner.rb +118 -84
  27. data/examples/dhanhq/schemas/agent_schemas.rb +2 -2
  28. data/examples/dhanhq/services/data_service.rb +5 -7
  29. data/examples/dhanhq/services/trading_service.rb +0 -3
  30. data/examples/dhanhq/technical_analysis_agentic_runner.rb +217 -84
  31. data/examples/dhanhq/technical_analysis_runner.rb +216 -162
  32. data/examples/dhanhq/test_tool_calling.rb +538 -0
  33. data/examples/dhanhq/test_tool_calling_verbose.rb +251 -0
  34. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +12 -17
  35. data/examples/dhanhq_agent.rb +159 -116
  36. data/examples/dhanhq_tools.rb +1158 -251
  37. data/examples/multi_step_agent_with_external_data.rb +368 -0
  38. data/examples/structured_tools.rb +89 -0
  39. data/examples/test_dhanhq_tool_calling.rb +375 -0
  40. data/examples/test_tool_calling.rb +160 -0
  41. data/examples/tool_calling_direct.rb +124 -0
  42. data/examples/tool_dto_example.rb +94 -0
  43. data/exe/dhan_console +4 -0
  44. data/exe/ollama-client +1 -1
  45. data/lib/ollama/agent/executor.rb +116 -15
  46. data/lib/ollama/client.rb +118 -55
  47. data/lib/ollama/config.rb +36 -0
  48. data/lib/ollama/dto.rb +187 -0
  49. data/lib/ollama/embeddings.rb +77 -0
  50. data/lib/ollama/options.rb +104 -0
  51. data/lib/ollama/response.rb +121 -0
  52. data/lib/ollama/tool/function/parameters/property.rb +72 -0
  53. data/lib/ollama/tool/function/parameters.rb +101 -0
  54. data/lib/ollama/tool/function.rb +78 -0
  55. data/lib/ollama/tool.rb +60 -0
  56. data/lib/ollama/version.rb +1 -1
  57. data/lib/ollama_client.rb +3 -0
  58. metadata +31 -3
  59. /data/{PRODUCTION_FIXES.md → docs/PRODUCTION_FIXES.md} +0 -0
  60. /data/{TESTING.md → docs/TESTING.md} +0 -0
@@ -77,77 +77,64 @@ puts
77
77
 
78
78
  # Helper methods (defined before use)
79
79
  def perform_technical_analysis(agent, symbol, exchange_segment)
80
- begin
81
- analysis_result = agent.analysis_agent.analyze_symbol(
82
- symbol: symbol,
83
- exchange_segment: exchange_segment
84
- )
80
+ analysis_result = agent.analysis_agent.analyze_symbol(
81
+ symbol: symbol,
82
+ exchange_segment: exchange_segment
83
+ )
85
84
 
86
- if analysis_result[:error]
87
- puts " ⚠️ Error: #{analysis_result[:error]}"
88
- return
89
- end
85
+ return log_analysis_error(analysis_result) if analysis_result[:error]
90
86
 
91
- analysis = analysis_result[:analysis]
92
- puts " ✅ Analysis Complete"
93
- puts " 📊 Trend: #{analysis[:trend]&.dig(:trend) || 'N/A'} (#{analysis[:trend]&.dig(:strength) || 0}% strength)"
94
- puts " 📊 RSI: #{analysis[:indicators]&.dig(:rsi)&.round(2) || 'N/A'}"
95
- puts " 📊 MACD: #{analysis[:indicators]&.dig(:macd)&.round(2) || 'N/A'}"
96
- puts " 📊 Current Price: #{analysis[:current_price] || 'N/A'}"
97
- puts " 📊 Patterns: #{analysis[:patterns]&.dig(:candlestick)&.length || 0} patterns"
98
- puts " 📊 Structure Break: #{analysis[:structure_break]&.dig(:broken) ? 'Yes' : 'No'}"
99
- rescue StandardError => e
100
- puts " ❌ Error: #{e.message}"
101
- end
87
+ analysis = analysis_result[:analysis]
88
+ return log_analysis_empty if analysis.nil? || analysis.empty?
89
+
90
+ log_analysis_summary(analysis)
91
+ rescue StandardError => e
92
+ puts " Error: #{e.message}"
102
93
  end
103
94
 
104
95
  def perform_swing_scan(agent, symbol, exchange_segment)
105
- begin
106
- candidates = agent.swing_scanner.scan_symbols(
107
- [symbol],
108
- exchange_segment: exchange_segment,
109
- min_score: 40,
110
- verbose: false
111
- )
96
+ candidates = agent.swing_scanner.scan_symbols(
97
+ [symbol],
98
+ exchange_segment: exchange_segment,
99
+ min_score: 40,
100
+ verbose: false
101
+ )
112
102
 
113
- if candidates.empty?
114
- puts " ⚠️ No swing candidates found (score < 40)"
115
- else
116
- candidate = candidates.first
117
- puts " ✅ Swing Candidate Found"
118
- puts " 📈 Score: #{candidate[:score]}/100"
119
- puts " 📊 Trend: #{candidate[:analysis][:trend][:trend]}"
120
- puts " 💡 #{candidate[:recommendation] || 'Analysis complete'}"
121
- end
122
- rescue StandardError => e
123
- puts " ❌ Error: #{e.message}"
103
+ if candidates.empty?
104
+ puts " ⚠️ No swing candidates found (score < 40)"
105
+ else
106
+ candidate = candidates.first
107
+ puts " ✅ Swing Candidate Found"
108
+ puts " 📈 Score: #{candidate[:score]}/100"
109
+ puts " 📊 Trend: #{candidate[:analysis][:trend][:trend]}"
110
+ puts " 💡 #{candidate[:recommendation] || 'Analysis complete'}"
124
111
  end
112
+ rescue StandardError => e
113
+ puts " ❌ Error: #{e.message}"
125
114
  end
126
115
 
127
116
  def perform_options_scan(agent, symbol, exchange_segment)
128
- begin
129
- options_setups = agent.options_scanner.scan_for_options_setups(
130
- symbol,
131
- exchange_segment: exchange_segment,
132
- min_score: 40,
133
- verbose: true
134
- )
117
+ options_setups = agent.options_scanner.scan_for_options_setups(
118
+ symbol,
119
+ exchange_segment: exchange_segment,
120
+ min_score: 40,
121
+ verbose: true
122
+ )
135
123
 
136
- if options_setups[:error]
137
- puts " ⚠️ #{options_setups[:error]}"
138
- elsif options_setups[:setups] && !options_setups[:setups].empty?
139
- puts " ✅ Found #{options_setups[:setups].length} options setups"
140
- options_setups[:setups].first(3).each do |setup|
141
- puts " 📊 #{setup[:type].to_s.upcase} @ #{setup[:strike]}: Score #{setup[:score]}/100"
142
- end
143
- else
144
- puts " ⚠️ No options setups found (min_score: 40)"
145
- puts " Try lowering min_score or check verbose output above for details"
124
+ if options_setups[:error]
125
+ puts " ⚠️ #{options_setups[:error]}"
126
+ elsif options_setups[:setups] && !options_setups[:setups].empty?
127
+ puts " ✅ Found #{options_setups[:setups].length} options setups"
128
+ options_setups[:setups].first(3).each do |setup|
129
+ puts " 📊 #{setup[:type].to_s.upcase} @ #{setup[:strike]}: Score #{setup[:score]}/100"
146
130
  end
147
- rescue StandardError => e
148
- puts " Error: #{e.message}"
149
- puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
131
+ else
132
+ puts " ⚠️ No options setups found (min_score: 40)"
133
+ puts " Try lowering min_score or check verbose output above for details"
150
134
  end
135
+ rescue StandardError => e
136
+ puts " ❌ Error: #{e.message}"
137
+ puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
151
138
  end
152
139
 
153
140
  # Initialize agent
@@ -160,11 +147,7 @@ puts "🤔 Letting LLM decide what to analyze..."
160
147
  puts "─" * 60
161
148
 
162
149
  # Get market context first (can be fetched from real data)
163
- market_context = if ARGV[1]
164
- ARGV[1]
165
- else
166
- "Current market: SENSEX, NIFTY and RELIANCE are active. Looking for trading opportunities."
167
- end
150
+ market_context = ARGV[1] || "Current market: SENSEX, NIFTY and RELIANCE are active. Looking for trading opportunities."
168
151
 
169
152
  user_query = ARGV[0] || "Analyze SENSEX and find swing trading opportunities"
170
153
 
@@ -183,7 +166,8 @@ begin
183
166
  puts " Falling back to default analysis..."
184
167
  plan = {
185
168
  "analysis_plan" => [
186
- { "symbol" => "RELIANCE", "exchange_segment" => "NSE_EQ", "analysis_type" => "technical_analysis", "priority" => 1 },
169
+ { "symbol" => "RELIANCE", "exchange_segment" => "NSE_EQ", "analysis_type" => "technical_analysis",
170
+ "priority" => 1 },
187
171
  { "symbol" => "TCS", "exchange_segment" => "NSE_EQ", "analysis_type" => "swing_scan", "priority" => 2 },
188
172
  { "symbol" => "NIFTY", "exchange_segment" => "IDX_I", "analysis_type" => "options_scan", "priority" => 3 }
189
173
  ],
@@ -208,13 +192,19 @@ begin
208
192
  puts "Task #{idx + 1}: #{analysis_type} for #{symbol} (#{exchange_segment})"
209
193
  puts "─" * 60
210
194
 
211
- case analysis_type
212
- when "technical_analysis", "all"
195
+ if analysis_type == "all"
213
196
  perform_technical_analysis(agent, symbol, exchange_segment)
214
- when "swing_scan", "all"
215
197
  perform_swing_scan(agent, symbol, exchange_segment)
216
- when "options_scan", "all"
217
198
  perform_options_scan(agent, symbol, exchange_segment)
199
+ else
200
+ case analysis_type
201
+ when "technical_analysis"
202
+ perform_technical_analysis(agent, symbol, exchange_segment)
203
+ when "swing_scan"
204
+ perform_swing_scan(agent, symbol, exchange_segment)
205
+ when "options_scan"
206
+ perform_options_scan(agent, symbol, exchange_segment)
207
+ end
218
208
  end
219
209
 
220
210
  puts
@@ -224,6 +214,70 @@ rescue StandardError => e
224
214
  puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
225
215
  end
226
216
 
217
+ def log_analysis_error(analysis_result)
218
+ puts " ⚠️ Error: #{analysis_result[:error]}"
219
+ end
220
+
221
+ def log_analysis_empty
222
+ puts " ⚠️ Error: Analysis returned empty result"
223
+ end
224
+
225
+ def log_analysis_summary(analysis)
226
+ puts " ✅ Analysis Complete"
227
+ puts trend_summary_text(analysis)
228
+ puts rsi_summary_text(analysis)
229
+ puts macd_summary_text(analysis)
230
+ puts current_price_text(analysis)
231
+ puts patterns_summary_text(analysis)
232
+ puts structure_break_text(analysis)
233
+ end
234
+
235
+ def trend_summary_text(analysis)
236
+ trend = analysis[:trend] || {}
237
+ trend_name = trend[:trend] || "N/A"
238
+ strength = trend[:strength] || 0
239
+ " 📊 Trend: #{trend_name} (#{strength}% strength)"
240
+ end
241
+
242
+ def rsi_summary_text(analysis)
243
+ rsi = analysis[:indicators]&.dig(:rsi)
244
+ rsi_text = rsi ? rsi.round(2) : "N/A"
245
+ " 📊 RSI: #{rsi_text}"
246
+ end
247
+
248
+ def macd_summary_text(analysis)
249
+ macd = analysis[:indicators]&.dig(:macd)
250
+ macd_text = macd ? macd.round(2) : "N/A"
251
+ " 📊 MACD: #{macd_text}"
252
+ end
253
+
254
+ def current_price_text(analysis)
255
+ current_price = analysis[:current_price] || "N/A"
256
+ " 📊 Current Price: #{current_price}"
257
+ end
258
+
259
+ def patterns_summary_text(analysis)
260
+ candlestick_patterns = analysis[:patterns]&.dig(:candlestick) || []
261
+ " 📊 Patterns: #{candlestick_patterns.length} patterns"
262
+ end
263
+
264
+ def structure_break_text(analysis)
265
+ broken = analysis[:structure_break]&.dig(:broken)
266
+ " 📊 Structure Break: #{broken ? 'Yes' : 'No'}"
267
+ end
268
+
269
+ def format_score_breakdown(details)
270
+ "Trend=#{details[:trend]}, RSI=#{details[:rsi]}, MACD=#{details[:macd]}, " \
271
+ "Structure=#{details[:structure]}, Patterns=#{details[:patterns]}"
272
+ end
273
+
274
+ def format_option_setup_details(setup)
275
+ iv = setup[:iv]&.round(2) || "N/A"
276
+ oi = setup[:oi] || "N/A"
277
+ volume = setup[:volume] || "N/A"
278
+ "IV: #{iv}% | OI: #{oi} | Volume: #{volume}"
279
+ end
280
+
227
281
  # Uncomment below to also run manual examples for comparison
228
282
  if ENV["SHOW_MANUAL_EXAMPLES"] == "true"
229
283
  puts "=" * 60
@@ -237,121 +291,121 @@ if ENV["SHOW_MANUAL_EXAMPLES"] == "true"
237
291
  puts "Example 1: Technical Analysis for RELIANCE"
238
292
  puts "─" * 60
239
293
 
240
- begin
241
- analysis_result = agent.analysis_agent.analyze_symbol(
242
- symbol: "RELIANCE",
243
- exchange_segment: "NSE_EQ"
244
- )
294
+ begin
295
+ analysis_result = agent.analysis_agent.analyze_symbol(
296
+ symbol: "RELIANCE",
297
+ exchange_segment: "NSE_EQ"
298
+ )
245
299
 
246
- if analysis_result[:error]
247
- puts " ⚠️ Error: #{analysis_result[:error]}"
248
- elsif analysis_result[:analysis].nil? || analysis_result[:analysis].empty?
249
- puts " ⚠️ Error: Analysis returned empty result"
250
- else
251
- analysis = analysis_result[:analysis]
252
- puts " ✅ Analysis Complete"
253
- puts " 📊 Trend: #{analysis[:trend]&.dig(:trend) || 'N/A'} (#{analysis[:trend]&.dig(:strength) || 0}% strength)"
254
- puts " 📊 RSI: #{analysis[:indicators]&.dig(:rsi)&.round(2) || 'N/A'}"
255
- puts " 📊 MACD: #{analysis[:indicators]&.dig(:macd)&.round(2) || 'N/A'}"
256
- puts " 📊 Current Price: #{analysis[:current_price] || 'N/A'}"
257
- puts " 📊 Patterns Detected: #{analysis[:patterns]&.dig(:candlestick)&.length || 0} candlestick patterns"
258
- puts " 📊 Structure Break: #{analysis[:structure_break]&.dig(:broken) ? 'Yes' : 'No'}"
259
-
260
- # Generate swing trading recommendation
261
- begin
262
- recommendation = agent.analysis_agent.generate_recommendation(
263
- analysis_result,
264
- trading_style: :swing
265
- )
266
-
267
- if recommendation && !recommendation[:error] && recommendation.is_a?(Hash)
268
- puts "\n 💡 Swing Trading Recommendation:"
269
- puts " Action: #{recommendation['recommendation']&.upcase || 'N/A'}"
270
- puts " Entry: #{recommendation['entry_price'] || 'N/A'}"
271
- puts " Stop Loss: #{recommendation['stop_loss'] || 'N/A'}"
272
- puts " Target: #{recommendation['target_price'] || 'N/A'}"
273
- puts " Risk/Reward: #{recommendation['risk_reward_ratio']&.round(2) || 'N/A'}"
274
- puts " Confidence: #{(recommendation['confidence'] * 100).round}%" if recommendation["confidence"]
300
+ if analysis_result[:error]
301
+ puts " ⚠️ Error: #{analysis_result[:error]}"
302
+ elsif analysis_result[:analysis].nil? || analysis_result[:analysis].empty?
303
+ puts " ⚠️ Error: Analysis returned empty result"
304
+ else
305
+ analysis = analysis_result[:analysis]
306
+ puts " ✅ Analysis Complete"
307
+ puts " 📊 Trend: #{analysis[:trend]&.dig(:trend) || 'N/A'} (#{analysis[:trend]&.dig(:strength) || 0}% strength)"
308
+ puts " 📊 RSI: #{analysis[:indicators]&.dig(:rsi)&.round(2) || 'N/A'}"
309
+ puts " 📊 MACD: #{analysis[:indicators]&.dig(:macd)&.round(2) || 'N/A'}"
310
+ puts " 📊 Current Price: #{analysis[:current_price] || 'N/A'}"
311
+ puts " 📊 Patterns Detected: #{analysis[:patterns]&.dig(:candlestick)&.length || 0} candlestick patterns"
312
+ puts " 📊 Structure Break: #{analysis[:structure_break]&.dig(:broken) ? 'Yes' : 'No'}"
313
+
314
+ # Generate swing trading recommendation
315
+ begin
316
+ recommendation = agent.analysis_agent.generate_recommendation(
317
+ analysis_result,
318
+ trading_style: :swing
319
+ )
320
+
321
+ if recommendation && !recommendation[:error] && recommendation.is_a?(Hash)
322
+ puts "\n 💡 Swing Trading Recommendation:"
323
+ puts " Action: #{recommendation['recommendation']&.upcase || 'N/A'}"
324
+ puts " Entry: #{recommendation['entry_price'] || 'N/A'}"
325
+ puts " Stop Loss: #{recommendation['stop_loss'] || 'N/A'}"
326
+ puts " Target: #{recommendation['target_price'] || 'N/A'}"
327
+ puts " Risk/Reward: #{recommendation['risk_reward_ratio']&.round(2) || 'N/A'}"
328
+ puts " Confidence: #{(recommendation['confidence'] * 100).round}%" if recommendation["confidence"]
329
+ end
330
+ rescue StandardError => e
331
+ puts " ⚠️ Could not generate recommendation: #{e.message}"
275
332
  end
276
- rescue StandardError => e
277
- puts " ⚠️ Could not generate recommendation: #{e.message}"
278
333
  end
334
+ rescue StandardError => e
335
+ puts " ❌ Error: #{e.message}"
336
+ puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
279
337
  end
280
- rescue StandardError => e
281
- puts " ❌ Error: #{e.message}"
282
- puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
283
- end
284
338
 
285
- puts
286
- puts "Example 2: Swing Trading Scanner"
287
- puts "─" * 60
339
+ puts
340
+ puts "Example 2: Swing Trading Scanner"
341
+ puts "─" * 60
288
342
 
289
- begin
290
- # Scan a few symbols for swing opportunities
291
- symbols_to_scan = ["RELIANCE", "TCS", "INFY"]
292
- puts " 🔍 Scanning #{symbols_to_scan.length} symbols for swing opportunities..."
343
+ begin
344
+ # Scan a few symbols for swing opportunities
345
+ symbols_to_scan = ["RELIANCE", "TCS", "INFY"]
346
+ puts " 🔍 Scanning #{symbols_to_scan.length} symbols for swing opportunities..."
293
347
 
294
- candidates = agent.swing_scanner.scan_symbols(
295
- symbols_to_scan,
296
- exchange_segment: "NSE_EQ",
297
- min_score: 40,
298
- verbose: true
299
- )
348
+ candidates = agent.swing_scanner.scan_symbols(
349
+ symbols_to_scan,
350
+ exchange_segment: "NSE_EQ",
351
+ min_score: 40,
352
+ verbose: true
353
+ )
300
354
 
301
- if candidates.empty?
302
- puts " ⚠️ No swing candidates found above minimum score (40/100)"
303
- puts " Try lowering min_score or check rejected candidates above"
304
- else
305
- puts " ✅ Found #{candidates.length} swing candidates:"
306
- candidates.each do |candidate|
307
- puts " 📈 #{candidate[:symbol]}: Score #{candidate[:score]}/100"
308
- if candidate[:score_details]
309
- details = candidate[:score_details]
310
- puts " Breakdown: Trend=#{details[:trend]}, RSI=#{details[:rsi]}, MACD=#{details[:macd]}, Structure=#{details[:structure]}, Patterns=#{details[:patterns]}"
355
+ if candidates.empty?
356
+ puts " ⚠️ No swing candidates found above minimum score (40/100)"
357
+ puts " Try lowering min_score or check rejected candidates above"
358
+ else
359
+ puts " ✅ Found #{candidates.length} swing candidates:"
360
+ candidates.each do |candidate|
361
+ puts " 📈 #{candidate[:symbol]}: Score #{candidate[:score]}/100"
362
+ if candidate[:score_details]
363
+ details = candidate[:score_details]
364
+ puts " Breakdown: #{format_score_breakdown(details)}"
365
+ end
366
+ trend = candidate[:analysis][:trend]
367
+ puts " Trend: #{trend[:trend]} (#{trend[:strength]}% strength)"
368
+ puts " #{candidate[:interpretation]}"
311
369
  end
312
- trend = candidate[:analysis][:trend]
313
- puts " Trend: #{trend[:trend]} (#{trend[:strength]}% strength)"
314
- puts " #{candidate[:interpretation]}"
315
370
  end
371
+ rescue StandardError => e
372
+ puts " ❌ Error: #{e.message}"
373
+ puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
316
374
  end
317
- rescue StandardError => e
318
- puts " ❌ Error: #{e.message}"
319
- puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
320
- end
321
375
 
322
- puts
323
- puts "Example 3: Intraday Options Scanner"
324
- puts "─" * 60
376
+ puts
377
+ puts "Example 3: Intraday Options Scanner"
378
+ puts "─" * 60
325
379
 
326
- begin
327
- puts " 🔍 Scanning NIFTY for intraday options opportunities..."
380
+ begin
381
+ puts " 🔍 Scanning NIFTY for intraday options opportunities..."
328
382
 
329
- options_setups = agent.options_scanner.scan_for_options_setups(
330
- "NIFTY",
331
- exchange_segment: "IDX_I",
332
- min_score: 40,
333
- verbose: true
334
- )
383
+ options_setups = agent.options_scanner.scan_for_options_setups(
384
+ "NIFTY",
385
+ exchange_segment: "IDX_I",
386
+ min_score: 40,
387
+ verbose: true
388
+ )
335
389
 
336
- if options_setups[:error]
337
- puts " ⚠️ #{options_setups[:error]}"
338
- elsif options_setups[:setups] && !options_setups[:setups].empty?
339
- puts " ✅ Found #{options_setups[:setups].length} options setups:"
340
- options_setups[:setups].each do |setup|
341
- puts " 📊 #{setup[:type].to_s.upcase} @ #{setup[:strike]}"
342
- puts " IV: #{setup[:iv]&.round(2) || 'N/A'}% | OI: #{setup[:oi] || 'N/A'} | Volume: #{setup[:volume] || 'N/A'}"
343
- puts " Score: #{setup[:score]}/100 | Recommendation: #{setup[:recommendation]}"
390
+ if options_setups[:error]
391
+ puts " ⚠️ #{options_setups[:error]}"
392
+ elsif options_setups[:setups] && !options_setups[:setups].empty?
393
+ puts " ✅ Found #{options_setups[:setups].length} options setups:"
394
+ options_setups[:setups].each do |setup|
395
+ puts " 📊 #{setup[:type].to_s.upcase} @ #{setup[:strike]}"
396
+ puts " #{format_option_setup_details(setup)}"
397
+ puts " Score: #{setup[:score]}/100 | Recommendation: #{setup[:recommendation]}"
398
+ end
399
+ else
400
+ puts " ⚠️ No options setups found above minimum score (40/100)"
401
+ puts " Check rejected setups above or try lowering min_score"
344
402
  end
345
- else
346
- puts " ⚠️ No options setups found above minimum score (40/100)"
347
- puts " Check rejected setups above or try lowering min_score"
348
- end
349
403
  rescue StandardError => e
350
404
  puts " ❌ Error: #{e.message}"
351
405
  puts " #{e.backtrace.first(3).join("\n ")}" if e.backtrace
352
406
  end
353
407
 
354
- end # End of SHOW_MANUAL_EXAMPLES block
408
+ end
355
409
 
356
410
  puts
357
411
  puts "=" * 60