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,247 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../analysis/trend_analyzer"
4
- require_relative "../services/data_service"
5
-
6
- module DhanHQ
7
- module Scanners
8
- # Scanner for swing trading candidates
9
- class SwingScanner
10
- def initialize
11
- @data_service = Services::DataService.new
12
- end
13
-
14
- def scan_symbols(symbols, exchange_segment: "NSE_EQ", min_score: 40, verbose: false)
15
- candidates = []
16
- rejected = []
17
-
18
- symbols.each do |symbol|
19
- analysis = analyze_symbol(symbol, exchange_segment)
20
-
21
- if analysis[:error]
22
- rejected << { symbol: symbol, reason: analysis[:error] }
23
- next
24
- end
25
-
26
- score = calculate_swing_score(analysis[:analysis])
27
- score_details = calculate_swing_score_details(analysis[:analysis])
28
-
29
- if score < min_score
30
- rejected << {
31
- symbol: symbol,
32
- score: score,
33
- reason: "Below minimum score (#{min_score})",
34
- details: score_details
35
- }
36
- next
37
- end
38
-
39
- candidates << {
40
- symbol: symbol,
41
- score: score,
42
- analysis: analysis[:analysis],
43
- recommendation: analysis[:interpretation],
44
- score_details: score_details
45
- }
46
- end
47
-
48
- if verbose && !rejected.empty?
49
- puts " 📋 Rejected candidates:"
50
- rejected.each do |r|
51
- puts " ❌ #{r[:symbol]}: Score #{r[:score]}/100 - #{r[:reason]}"
52
- puts " Breakdown: #{format_score_breakdown(r[:details])}" if r[:details]
53
- end
54
- end
55
-
56
- candidates.sort_by { |c| -c[:score] }
57
- end
58
-
59
- def scan_by_criteria(_criteria = {})
60
- # This would typically fetch a list of symbols from an API
61
- # For now, return empty - would need symbol list source
62
- []
63
- end
64
-
65
- private
66
-
67
- def analyze_symbol(symbol, exchange_segment)
68
- result = @data_service.execute(
69
- action: "get_historical_data",
70
- params: {
71
- "symbol" => symbol,
72
- "exchange_segment" => exchange_segment,
73
- "from_date" => (Date.today - 90).strftime("%Y-%m-%d"),
74
- "to_date" => Date.today.strftime("%Y-%m-%d")
75
- }
76
- )
77
-
78
- return { error: "Failed to fetch data: #{result[:error] || 'Unknown error'}" } if result[:error]
79
- return { error: "No result data returned" } unless result[:result]
80
-
81
- ohlc_data = convert_to_ohlc(result)
82
- return { error: "Failed to convert data to OHLC format" } if ohlc_data.nil? || ohlc_data.empty?
83
-
84
- analysis = Analysis::TrendAnalyzer.analyze(ohlc_data)
85
- return { error: "Analysis returned empty result" } if analysis.nil? || analysis.empty?
86
-
87
- {
88
- symbol: symbol,
89
- analysis: analysis,
90
- interpretation: interpret_for_swing(analysis)
91
- }
92
- end
93
-
94
- def calculate_swing_score(analysis)
95
- details = calculate_swing_score_details(analysis)
96
- details.values.sum
97
- end
98
-
99
- def calculate_swing_score_details(analysis)
100
- return empty_score_details if analysis.nil? || analysis.empty?
101
-
102
- {
103
- trend: trend_points(analysis),
104
- rsi: rsi_points(analysis),
105
- macd: macd_points(analysis),
106
- structure: structure_points(analysis),
107
- patterns: pattern_points(analysis)
108
- }
109
- end
110
-
111
- def interpret_for_swing(analysis)
112
- return "Unable to analyze" if analysis.nil? || analysis.empty?
113
-
114
- trend = analysis[:trend]&.dig(:trend)
115
- rsi = analysis[:indicators]&.dig(:rsi)
116
- structure_break = analysis[:structure_break]
117
-
118
- if trend == :uptrend && rsi && rsi < 70 && structure_break && structure_break[:broken]
119
- "Strong swing candidate - uptrend with bullish structure break"
120
- elsif trend == :uptrend && rsi&.between?(40, 60)
121
- "Good swing candidate - healthy uptrend, RSI in good zone"
122
- else
123
- "Moderate candidate - review individual factors"
124
- end
125
- end
126
-
127
- def convert_to_ohlc(historical_data)
128
- return [] unless historical_data.is_a?(Hash)
129
-
130
- data = extract_data_payload(historical_data)
131
- return [] unless data
132
-
133
- return ohlc_from_hash(data) if data.is_a?(Hash)
134
- return ohlc_from_array(data) if data.is_a?(Array)
135
-
136
- []
137
- end
138
-
139
- def format_score_breakdown(details)
140
- "Trend=#{details[:trend]}, RSI=#{details[:rsi]}, MACD=#{details[:macd]}, " \
141
- "Structure=#{details[:structure]}, Patterns=#{details[:patterns]}"
142
- end
143
-
144
- def empty_score_details
145
- { trend: 0, rsi: 0, macd: 0, structure: 0, patterns: 0 }
146
- end
147
-
148
- def trend_points(analysis)
149
- trend = analysis[:trend]
150
- return 0 unless trend && trend[:trend] == :uptrend
151
-
152
- (trend[:strength] || 0).clamp(0, 30)
153
- end
154
-
155
- def rsi_points(analysis)
156
- rsi = analysis[:indicators]&.dig(:rsi)
157
- return 0 unless rsi
158
- return 20 if rsi.between?(40, 60)
159
- return 10 if rsi.between?(30, 70)
160
-
161
- 0
162
- end
163
-
164
- def macd_points(analysis)
165
- macd = analysis[:indicators]&.dig(:macd)
166
- signal = analysis[:indicators]&.dig(:macd_signal)
167
- return 0 unless macd && signal
168
-
169
- return 20 if macd > signal
170
- return 10 if macd > signal * 0.9
171
-
172
- 0
173
- end
174
-
175
- def structure_points(analysis)
176
- structure_break = analysis[:structure_break]
177
- return 0 unless structure_break && structure_break[:broken]
178
- return 0 unless structure_break[:direction] == :bullish_break
179
-
180
- 15
181
- end
182
-
183
- def pattern_points(analysis)
184
- patterns = analysis[:patterns]&.dig(:candlestick) || []
185
- bullish_patterns = patterns.count { |pattern| pattern.is_a?(Hash) && pattern[:type].to_s.include?("bullish") }
186
- [bullish_patterns * 5, 15].min
187
- end
188
-
189
- def extract_data_payload(historical_data)
190
- outer_result = historical_data[:result] || historical_data["result"]
191
- return nil unless outer_result.is_a?(Hash)
192
-
193
- outer_result[:data] || outer_result["data"]
194
- end
195
-
196
- def ohlc_from_hash(data)
197
- series = extract_series(data)
198
- return [] if series[:closes].empty?
199
-
200
- max_length = series_lengths(series).max
201
- build_ohlc_rows(series, max_length)
202
- end
203
-
204
- def extract_series(data)
205
- {
206
- opens: data[:open] || data["open"] || [],
207
- highs: data[:high] || data["high"] || [],
208
- lows: data[:low] || data["low"] || [],
209
- closes: data[:close] || data["close"] || [],
210
- volumes: data[:volume] || data["volume"] || []
211
- }
212
- end
213
-
214
- def series_lengths(series)
215
- [series[:opens].length, series[:highs].length, series[:lows].length, series[:closes].length]
216
- end
217
-
218
- def build_ohlc_rows(series, max_length)
219
- (0...max_length).map do |index|
220
- {
221
- open: series[:opens][index],
222
- high: series[:highs][index],
223
- low: series[:lows][index],
224
- close: series[:closes][index],
225
- volume: series[:volumes][index] || 0
226
- }
227
- end
228
- end
229
-
230
- def ohlc_from_array(data)
231
- data.filter_map { |bar| normalize_bar(bar) }
232
- end
233
-
234
- def normalize_bar(bar)
235
- return nil unless bar.is_a?(Hash)
236
-
237
- {
238
- open: bar["open"] || bar[:open],
239
- high: bar["high"] || bar[:high],
240
- low: bar["low"] || bar[:low],
241
- close: bar["close"] || bar[:close],
242
- volume: bar["volume"] || bar[:volume]
243
- }
244
- end
245
- end
246
- end
247
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DhanHQ
4
- module Schemas
5
- # JSON schemas for agent decision-making
6
- class AgentSchemas
7
- DATA_AGENT_SCHEMA = {
8
- "type" => "object",
9
- "required" => ["action", "reasoning", "confidence"],
10
- "properties" => {
11
- "action" => {
12
- "type" => "string",
13
- "enum" => ["get_market_quote", "get_live_ltp", "get_market_depth", "get_historical_data",
14
- "get_expired_options_data", "get_option_chain", "no_action"]
15
- },
16
- "reasoning" => {
17
- "type" => "string",
18
- "description" => "Why this action was chosen"
19
- },
20
- "confidence" => {
21
- "type" => "number",
22
- "minimum" => 0,
23
- "maximum" => 1,
24
- "description" => "Confidence in this decision (0.0 to 1.0, where 1.0 is 100% confident)"
25
- },
26
- "parameters" => {
27
- "type" => "object",
28
- "additionalProperties" => true,
29
- "description" => "Parameters for the action (symbol, exchange_segment, etc.)"
30
- }
31
- }
32
- }.freeze
33
-
34
- TRADING_AGENT_SCHEMA = {
35
- "type" => "object",
36
- "required" => ["action", "reasoning", "confidence"],
37
- "properties" => {
38
- "action" => {
39
- "type" => "string",
40
- "enum" => ["place_order", "place_super_order", "cancel_order", "no_action"]
41
- },
42
- "reasoning" => {
43
- "type" => "string",
44
- "description" => "Why this action was chosen"
45
- },
46
- "confidence" => {
47
- "type" => "number",
48
- "minimum" => 0,
49
- "maximum" => 1,
50
- "description" => "Confidence in this decision (0.0 to 1.0, where 1.0 is 100% confident)"
51
- },
52
- "parameters" => {
53
- "type" => "object",
54
- "additionalProperties" => true,
55
- "description" => "Parameters for the action (security_id, quantity, price, etc.)"
56
- }
57
- }
58
- }.freeze
59
- end
60
- end
61
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../utils/instrument_helper"
4
- require_relative "../utils/rate_limiter"
5
-
6
- module DhanHQ
7
- module Services
8
- # Base service class with common functionality
9
- class BaseService
10
- protected
11
-
12
- def find_instrument(exchange_segment, symbol)
13
- DhanHQ::Models::Instrument.find(exchange_segment, symbol)
14
- end
15
-
16
- def safe_attr(instrument, attr_name)
17
- DhanHQ::Utils::InstrumentHelper.safe_attr(instrument, attr_name)
18
- end
19
-
20
- def extract_value(data, keys)
21
- DhanHQ::Utils::InstrumentHelper.extract_value(data, keys)
22
- end
23
-
24
- def rate_limit_marketfeed
25
- DhanHQ::Utils::RateLimiter.marketfeed
26
- end
27
-
28
- def resolve_security_id(security_id, symbol, exchange_segment)
29
- return security_id.to_s if security_id
30
-
31
- instrument = find_instrument(exchange_segment, symbol.to_s)
32
- return nil unless instrument
33
-
34
- safe_attr(instrument, :security_id)&.to_s
35
- end
36
-
37
- def error_response(action, error, params = {})
38
- { action: action, error: error, params: params }
39
- end
40
-
41
- def success_response(action, result, params = {})
42
- { action: action, params: params, result: result }
43
- end
44
- end
45
- end
46
- end
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_service"
4
- require_relative "../../dhanhq_tools"
5
-
6
- module DhanHQ
7
- module Services
8
- # Service for executing data retrieval actions
9
- class DataService < BaseService
10
- def execute(action:, params:)
11
- case action
12
- when "get_market_quote"
13
- execute_market_quote(params)
14
- when "get_live_ltp"
15
- execute_live_ltp(params)
16
- when "get_market_depth"
17
- execute_market_depth(params)
18
- when "get_historical_data"
19
- execute_historical_data(params)
20
- when "get_option_chain"
21
- execute_option_chain(params)
22
- when "get_expired_options_data"
23
- execute_expired_options_data(params)
24
- when "no_action"
25
- { action: "no_action", message: "No action taken" }
26
- else
27
- { action: "unknown", error: "Unknown action: #{action}" }
28
- end
29
- end
30
-
31
- private
32
-
33
- def execute_market_quote(params)
34
- validate_symbol_or_id(params, "get_market_quote") ||
35
- DhanHQDataTools.get_market_quote(
36
- symbol: params["symbol"],
37
- security_id: params["security_id"],
38
- exchange_segment: params["exchange_segment"] || "NSE_EQ"
39
- )
40
- end
41
-
42
- def execute_live_ltp(params)
43
- validate_symbol_or_id(params, "get_live_ltp") ||
44
- DhanHQDataTools.get_live_ltp(
45
- symbol: params["symbol"],
46
- security_id: params["security_id"],
47
- exchange_segment: params["exchange_segment"] || "NSE_EQ"
48
- )
49
- end
50
-
51
- def execute_market_depth(params)
52
- validate_symbol_or_id(params, "get_market_depth") ||
53
- DhanHQDataTools.get_market_depth(
54
- symbol: params["symbol"],
55
- security_id: params["security_id"],
56
- exchange_segment: params["exchange_segment"] || "NSE_EQ"
57
- )
58
- end
59
-
60
- def execute_historical_data(params)
61
- validate_symbol_or_id(params, "get_historical_data") ||
62
- DhanHQDataTools.get_historical_data(
63
- symbol: params["symbol"],
64
- security_id: params["security_id"],
65
- exchange_segment: params["exchange_segment"] || "NSE_EQ",
66
- from_date: params["from_date"],
67
- to_date: params["to_date"],
68
- interval: params["interval"],
69
- expiry_code: params["expiry_code"]
70
- )
71
- end
72
-
73
- def execute_option_chain(params)
74
- validate_symbol_or_id(params, "get_option_chain") ||
75
- DhanHQDataTools.get_option_chain(
76
- symbol: params["symbol"],
77
- security_id: params["security_id"],
78
- exchange_segment: params["exchange_segment"] || "NSE_EQ",
79
- expiry: params["expiry"]
80
- )
81
- end
82
-
83
- def execute_expired_options_data(params)
84
- if missing_expired_options_params?(params)
85
- return error_response("get_expired_options_data",
86
- "Either symbol or security_id, and expiry_date are required",
87
- params)
88
- end
89
-
90
- DhanHQDataTools.get_expired_options_data(
91
- symbol: params["symbol"],
92
- security_id: params["security_id"],
93
- exchange_segment: params["exchange_segment"] || "NSE_FNO",
94
- expiry_date: params["expiry_date"],
95
- expiry_code: params["expiry_code"],
96
- interval: params["interval"] || "1",
97
- instrument: params["instrument"],
98
- expiry_flag: params["expiry_flag"] || "MONTH",
99
- strike: params["strike"] || "ATM",
100
- drv_option_type: params["drv_option_type"] || "CALL",
101
- required_data: params["required_data"]
102
- )
103
- end
104
-
105
- def validate_symbol_or_id(params, action)
106
- return nil unless params["symbol"].nil? && (params["security_id"].nil? || params["security_id"].to_s.empty?)
107
-
108
- error_response(action, "Either symbol or security_id is required", params)
109
- end
110
-
111
- def missing_expired_options_params?(params)
112
- symbol_or_id_missing = params["symbol"].nil? &&
113
- (params["security_id"].nil? || params["security_id"].to_s.empty?)
114
- symbol_or_id_missing || params["expiry_date"].nil?
115
- end
116
- end
117
- end
118
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_service"
4
- require_relative "../../dhanhq_tools"
5
-
6
- module DhanHQ
7
- module Services
8
- # Service for executing trading order actions
9
- class TradingService < BaseService
10
- def execute(action:, params:)
11
- case action
12
- when "place_order"
13
- execute_place_order(params)
14
- when "place_super_order"
15
- execute_place_super_order(params)
16
- when "cancel_order"
17
- execute_cancel_order(params)
18
- when "no_action"
19
- { action: "no_action", message: "No action taken" }
20
- else
21
- { action: "unknown", error: "Unknown action: #{action}" }
22
- end
23
- end
24
-
25
- private
26
-
27
- def execute_place_order(params)
28
- DhanHQTradingTools.build_order_params(
29
- transaction_type: params["transaction_type"] || "BUY",
30
- exchange_segment: params["exchange_segment"] || "NSE_EQ",
31
- product_type: params["product_type"] || "MARGIN",
32
- order_type: params["order_type"] || "LIMIT",
33
- security_id: params["security_id"],
34
- quantity: params["quantity"] || 1,
35
- price: params["price"]
36
- )
37
- end
38
-
39
- def execute_place_super_order(params)
40
- DhanHQTradingTools.build_super_order_params(
41
- transaction_type: params["transaction_type"] || "BUY",
42
- exchange_segment: params["exchange_segment"] || "NSE_EQ",
43
- product_type: params["product_type"] || "MARGIN",
44
- order_type: params["order_type"] || "LIMIT",
45
- security_id: params["security_id"],
46
- quantity: params["quantity"] || 1,
47
- price: params["price"],
48
- target_price: params["target_price"],
49
- stop_loss_price: params["stop_loss_price"],
50
- trailing_jump: params["trailing_jump"] || 10
51
- )
52
- end
53
-
54
- def execute_cancel_order(params)
55
- DhanHQTradingTools.build_cancel_params(order_id: params["order_id"])
56
- end
57
- end
58
- end
59
- end