ollama-client 0.2.5 → 0.2.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +138 -76
- data/docs/EXAMPLE_REORGANIZATION.md +412 -0
- data/docs/GETTING_STARTED.md +361 -0
- data/docs/INTEGRATION_TESTING.md +170 -0
- data/docs/NEXT_STEPS_SUMMARY.md +114 -0
- data/docs/PERSONAS.md +383 -0
- data/docs/QUICK_START.md +195 -0
- data/docs/TESTING.md +392 -170
- data/docs/TEST_CHECKLIST.md +450 -0
- data/examples/README.md +51 -66
- data/examples/basic_chat.rb +33 -0
- data/examples/basic_generate.rb +29 -0
- data/examples/tool_calling_parsing.rb +59 -0
- data/exe/ollama-client +128 -1
- data/lib/ollama/agent/planner.rb +7 -2
- data/lib/ollama/chat_session.rb +101 -0
- data/lib/ollama/client.rb +41 -35
- data/lib/ollama/config.rb +4 -1
- data/lib/ollama/document_loader.rb +1 -1
- data/lib/ollama/embeddings.rb +41 -26
- data/lib/ollama/errors.rb +1 -0
- data/lib/ollama/personas.rb +287 -0
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama_client.rb +7 -0
- metadata +14 -48
- data/examples/advanced_complex_schemas.rb +0 -366
- data/examples/advanced_edge_cases.rb +0 -241
- data/examples/advanced_error_handling.rb +0 -200
- data/examples/advanced_multi_step_agent.rb +0 -341
- data/examples/advanced_performance_testing.rb +0 -186
- data/examples/chat_console.rb +0 -143
- data/examples/complete_workflow.rb +0 -245
- data/examples/dhan_console.rb +0 -843
- data/examples/dhanhq/README.md +0 -236
- data/examples/dhanhq/agents/base_agent.rb +0 -74
- data/examples/dhanhq/agents/data_agent.rb +0 -66
- data/examples/dhanhq/agents/orchestrator_agent.rb +0 -120
- data/examples/dhanhq/agents/technical_analysis_agent.rb +0 -252
- data/examples/dhanhq/agents/trading_agent.rb +0 -81
- data/examples/dhanhq/analysis/market_structure.rb +0 -138
- data/examples/dhanhq/analysis/pattern_recognizer.rb +0 -192
- data/examples/dhanhq/analysis/trend_analyzer.rb +0 -88
- data/examples/dhanhq/builders/market_context_builder.rb +0 -67
- data/examples/dhanhq/dhanhq_agent.rb +0 -829
- data/examples/dhanhq/indicators/technical_indicators.rb +0 -158
- data/examples/dhanhq/scanners/intraday_options_scanner.rb +0 -492
- data/examples/dhanhq/scanners/swing_scanner.rb +0 -247
- data/examples/dhanhq/schemas/agent_schemas.rb +0 -61
- data/examples/dhanhq/services/base_service.rb +0 -46
- data/examples/dhanhq/services/data_service.rb +0 -118
- data/examples/dhanhq/services/trading_service.rb +0 -59
- data/examples/dhanhq/technical_analysis_agentic_runner.rb +0 -411
- data/examples/dhanhq/technical_analysis_runner.rb +0 -420
- data/examples/dhanhq/test_tool_calling.rb +0 -538
- data/examples/dhanhq/test_tool_calling_verbose.rb +0 -251
- data/examples/dhanhq/utils/instrument_helper.rb +0 -32
- data/examples/dhanhq/utils/parameter_cleaner.rb +0 -28
- data/examples/dhanhq/utils/parameter_normalizer.rb +0 -45
- data/examples/dhanhq/utils/rate_limiter.rb +0 -23
- data/examples/dhanhq/utils/trading_parameter_normalizer.rb +0 -72
- data/examples/dhanhq_agent.rb +0 -964
- data/examples/dhanhq_tools.rb +0 -1663
- data/examples/multi_step_agent_with_external_data.rb +0 -368
- data/examples/structured_outputs_chat.rb +0 -72
- data/examples/structured_tools.rb +0 -89
- data/examples/test_dhanhq_tool_calling.rb +0 -375
- data/examples/test_tool_calling.rb +0 -160
- data/examples/tool_calling_direct.rb +0 -124
- data/examples/tool_calling_pattern.rb +0 -269
- data/exe/dhan_console +0 -4
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# DhanHQ Tool Calling Test - Verbose Version
|
|
5
|
-
# Shows the complete tool calling flow: LLM decides → Executor executes
|
|
6
|
-
|
|
7
|
-
require_relative "../../lib/ollama_client"
|
|
8
|
-
require_relative "../dhanhq_tools"
|
|
9
|
-
|
|
10
|
-
puts "\n=== DHANHQ TOOL CALLING TEST (VERBOSE) ===\n"
|
|
11
|
-
puts "This demonstrates REAL tool calling:\n"
|
|
12
|
-
puts " 1. LLM receives user query + tool definitions"
|
|
13
|
-
puts " 2. LLM DECIDES which tools to call (not your code!)"
|
|
14
|
-
puts " 3. LLM returns tool_calls in response"
|
|
15
|
-
puts " 4. Executor detects tool_calls and executes callables"
|
|
16
|
-
puts " 5. Tool results fed back to LLM"
|
|
17
|
-
puts " 6. LLM generates final answer\n"
|
|
18
|
-
|
|
19
|
-
# Configure DhanHQ
|
|
20
|
-
begin
|
|
21
|
-
DhanHQ.configure_with_env
|
|
22
|
-
puts "✅ DhanHQ configured\n"
|
|
23
|
-
rescue StandardError => e
|
|
24
|
-
puts "⚠️ DhanHQ configuration error: #{e.message}\n"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Create client
|
|
28
|
-
config = Ollama::Config.new
|
|
29
|
-
config.model = ENV.fetch("OLLAMA_MODEL", "llama3.1:8b")
|
|
30
|
-
config.temperature = 0.2
|
|
31
|
-
config.timeout = 60
|
|
32
|
-
client = Ollama::Client.new(config: config)
|
|
33
|
-
|
|
34
|
-
# Define tools
|
|
35
|
-
market_quote_tool = Ollama::Tool.new(
|
|
36
|
-
type: "function",
|
|
37
|
-
function: Ollama::Tool::Function.new(
|
|
38
|
-
name: "get_market_quote",
|
|
39
|
-
description: "Get market quote for a symbol",
|
|
40
|
-
parameters: Ollama::Tool::Function::Parameters.new(
|
|
41
|
-
type: "object",
|
|
42
|
-
properties: {
|
|
43
|
-
symbol: Ollama::Tool::Function::Parameters::Property.new(
|
|
44
|
-
type: "string",
|
|
45
|
-
description: "Stock symbol"
|
|
46
|
-
),
|
|
47
|
-
exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
|
|
48
|
-
type: "string",
|
|
49
|
-
description: "Exchange segment",
|
|
50
|
-
enum: %w[NSE_EQ BSE_EQ IDX_I]
|
|
51
|
-
)
|
|
52
|
-
},
|
|
53
|
-
required: %w[symbol exchange_segment]
|
|
54
|
-
)
|
|
55
|
-
)
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
live_ltp_tool = Ollama::Tool.new(
|
|
59
|
-
type: "function",
|
|
60
|
-
function: Ollama::Tool::Function.new(
|
|
61
|
-
name: "get_live_ltp",
|
|
62
|
-
description: "Get live last traded price",
|
|
63
|
-
parameters: Ollama::Tool::Function::Parameters.new(
|
|
64
|
-
type: "object",
|
|
65
|
-
properties: {
|
|
66
|
-
symbol: Ollama::Tool::Function::Parameters::Property.new(
|
|
67
|
-
type: "string",
|
|
68
|
-
description: "Stock symbol"
|
|
69
|
-
),
|
|
70
|
-
exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
|
|
71
|
-
type: "string",
|
|
72
|
-
description: "Exchange segment",
|
|
73
|
-
enum: %w[NSE_EQ BSE_EQ IDX_I]
|
|
74
|
-
)
|
|
75
|
-
},
|
|
76
|
-
required: %w[symbol exchange_segment]
|
|
77
|
-
)
|
|
78
|
-
)
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
option_chain_tool = Ollama::Tool.new(
|
|
82
|
-
type: "function",
|
|
83
|
-
function: Ollama::Tool::Function.new(
|
|
84
|
-
name: "get_option_chain",
|
|
85
|
-
description: "Get option chain for an index (NIFTY, SENSEX, BANKNIFTY). " \
|
|
86
|
-
"Returns available expiries and option chain data with strikes, Greeks, OI, and IV.",
|
|
87
|
-
parameters: Ollama::Tool::Function::Parameters.new(
|
|
88
|
-
type: "object",
|
|
89
|
-
properties: {
|
|
90
|
-
symbol: Ollama::Tool::Function::Parameters::Property.new(
|
|
91
|
-
type: "string",
|
|
92
|
-
description: "Index symbol (NIFTY, SENSEX, or BANKNIFTY)",
|
|
93
|
-
enum: %w[NIFTY SENSEX BANKNIFTY]
|
|
94
|
-
),
|
|
95
|
-
exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
|
|
96
|
-
type: "string",
|
|
97
|
-
description: "Exchange segment (must be IDX_I for indices)",
|
|
98
|
-
enum: %w[IDX_I]
|
|
99
|
-
),
|
|
100
|
-
expiry: Ollama::Tool::Function::Parameters::Property.new(
|
|
101
|
-
type: "string",
|
|
102
|
-
description: "Optional expiry date (YYYY-MM-DD format). If not provided, returns available expiries list."
|
|
103
|
-
)
|
|
104
|
-
},
|
|
105
|
-
required: %w[symbol exchange_segment]
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
# Define callables (these are just implementations - LLM decides when to call them)
|
|
111
|
-
tools = {
|
|
112
|
-
"get_option_chain" => {
|
|
113
|
-
tool: option_chain_tool,
|
|
114
|
-
callable: lambda do |symbol:, exchange_segment:, expiry: nil|
|
|
115
|
-
# Normalize empty string to nil (LLM might pass "" when expiry is optional)
|
|
116
|
-
expiry = nil if expiry.is_a?(String) && expiry.empty?
|
|
117
|
-
puts "\n [TOOL EXECUTION] get_option_chain called by Executor"
|
|
118
|
-
puts " Args: symbol=#{symbol}, exchange_segment=#{exchange_segment}, expiry=#{expiry || 'nil'}"
|
|
119
|
-
puts " Note: This is called AFTER LLM decided to use this tool!"
|
|
120
|
-
result = DhanHQDataTools.get_option_chain(
|
|
121
|
-
symbol: symbol.to_s,
|
|
122
|
-
exchange_segment: exchange_segment.to_s,
|
|
123
|
-
expiry: expiry
|
|
124
|
-
)
|
|
125
|
-
if result[:error]
|
|
126
|
-
{ error: result[:error] }
|
|
127
|
-
elsif result[:result] && result[:result][:expiries]
|
|
128
|
-
# Return expiry list
|
|
129
|
-
{
|
|
130
|
-
symbol: symbol,
|
|
131
|
-
expiries_available: result[:result][:expiries],
|
|
132
|
-
count: result[:result][:count]
|
|
133
|
-
}
|
|
134
|
-
elsif result[:result] && result[:result][:chain]
|
|
135
|
-
# Return option chain data
|
|
136
|
-
chain = result[:result][:chain]
|
|
137
|
-
strikes = chain.is_a?(Hash) ? chain.keys.sort_by(&:to_f) : []
|
|
138
|
-
{
|
|
139
|
-
symbol: symbol,
|
|
140
|
-
expiry: result[:result][:expiry],
|
|
141
|
-
underlying_price: result[:result][:underlying_last_price],
|
|
142
|
-
strikes_count: strikes.length,
|
|
143
|
-
sample_strikes: strikes.first(5)
|
|
144
|
-
}
|
|
145
|
-
else
|
|
146
|
-
{ error: "Unexpected response format" }
|
|
147
|
-
end
|
|
148
|
-
rescue StandardError => e
|
|
149
|
-
{ error: e.message }
|
|
150
|
-
end
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
"get_market_quote" => {
|
|
154
|
-
tool: market_quote_tool,
|
|
155
|
-
callable: lambda do |symbol:, exchange_segment:|
|
|
156
|
-
puts "\n [TOOL EXECUTION] get_market_quote called by Executor"
|
|
157
|
-
puts " Args: symbol=#{symbol}, exchange_segment=#{exchange_segment}"
|
|
158
|
-
puts " Note: This is called AFTER LLM decided to use this tool!"
|
|
159
|
-
result = DhanHQDataTools.get_market_quote(
|
|
160
|
-
symbol: symbol.to_s,
|
|
161
|
-
exchange_segment: exchange_segment.to_s
|
|
162
|
-
)
|
|
163
|
-
if result[:error]
|
|
164
|
-
{ error: result[:error] }
|
|
165
|
-
else
|
|
166
|
-
quote = result[:result][:quote]
|
|
167
|
-
{
|
|
168
|
-
symbol: symbol,
|
|
169
|
-
last_price: quote[:last_price],
|
|
170
|
-
volume: quote[:volume],
|
|
171
|
-
ohlc: quote[:ohlc]
|
|
172
|
-
}
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
"get_live_ltp" => {
|
|
178
|
-
tool: live_ltp_tool,
|
|
179
|
-
callable: lambda do |symbol:, exchange_segment:|
|
|
180
|
-
puts "\n [TOOL EXECUTION] get_live_ltp called by Executor"
|
|
181
|
-
puts " Args: symbol=#{symbol}, exchange_segment=#{exchange_segment}"
|
|
182
|
-
puts " Note: This is called AFTER LLM decided to use this tool!"
|
|
183
|
-
sleep(1.2) # Rate limiting
|
|
184
|
-
result = DhanHQDataTools.get_live_ltp(
|
|
185
|
-
symbol: symbol.to_s,
|
|
186
|
-
exchange_segment: exchange_segment.to_s
|
|
187
|
-
)
|
|
188
|
-
if result[:error]
|
|
189
|
-
{ error: result[:error] }
|
|
190
|
-
else
|
|
191
|
-
{
|
|
192
|
-
symbol: symbol,
|
|
193
|
-
ltp: result[:result][:ltp]
|
|
194
|
-
}
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
puts "--- Step 1: Show what tools are available to LLM ---"
|
|
201
|
-
puts "Tools defined:"
|
|
202
|
-
puts " - get_market_quote: Get market quote"
|
|
203
|
-
puts " - get_live_ltp: Get live price"
|
|
204
|
-
puts " - get_option_chain: Get option chain for indices (NIFTY, SENSEX, BANKNIFTY)"
|
|
205
|
-
puts "\nThese tool DEFINITIONS are sent to LLM (not executed yet)\n"
|
|
206
|
-
|
|
207
|
-
puts "--- Step 2: LLM receives query and DECIDES which tools to call ---"
|
|
208
|
-
puts "User query: 'Get RELIANCE quote, NIFTY price, and SENSEX option chain'\n"
|
|
209
|
-
puts "LLM will analyze this and decide to call:"
|
|
210
|
-
puts " 1. get_market_quote(RELIANCE, NSE_EQ)"
|
|
211
|
-
puts " 2. get_live_ltp(NIFTY, IDX_I)"
|
|
212
|
-
puts " 3. get_option_chain(SENSEX, IDX_I)"
|
|
213
|
-
puts "\nThis decision is made by the LLM, not by your code!\n"
|
|
214
|
-
|
|
215
|
-
puts "--- Step 3: Executor sends request to LLM with tool definitions ---"
|
|
216
|
-
puts "Sending to LLM via chat_raw() with tools parameter...\n"
|
|
217
|
-
|
|
218
|
-
# Create executor
|
|
219
|
-
executor = Ollama::Agent::Executor.new(
|
|
220
|
-
client,
|
|
221
|
-
tools: tools,
|
|
222
|
-
max_steps: 10
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
begin
|
|
226
|
-
result = executor.run(
|
|
227
|
-
system: "You are a market data assistant. Use the available tools to get market data. " \
|
|
228
|
-
"For option chains, you can get SENSEX options using get_option_chain with " \
|
|
229
|
-
"symbol='SENSEX' and exchange_segment='IDX_I'.",
|
|
230
|
-
user: "Get market quote for RELIANCE stock, check NIFTY's current price, and get SENSEX option chain"
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
puts "\n--- Step 4: Final result from LLM (after tool execution) ---"
|
|
234
|
-
puts "=" * 60
|
|
235
|
-
puts result
|
|
236
|
-
puts "=" * 60
|
|
237
|
-
rescue Ollama::Error => e
|
|
238
|
-
puts "\n❌ Error: #{e.message}"
|
|
239
|
-
rescue StandardError => e
|
|
240
|
-
puts "\n❌ Unexpected error: #{e.message}"
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
puts "\n--- Summary ---"
|
|
244
|
-
puts "✅ This IS real tool calling:"
|
|
245
|
-
puts " - LLM decides which tools to call (not your code)"
|
|
246
|
-
puts " - LLM returns tool_calls in response"
|
|
247
|
-
puts " - Executor detects tool_calls and executes callables"
|
|
248
|
-
puts " - Tool results fed back to LLM automatically"
|
|
249
|
-
puts " - LLM generates final answer based on tool results"
|
|
250
|
-
puts "\nThe callables are just implementations - the LLM decides WHEN to call them!"
|
|
251
|
-
puts "\n=== DONE ===\n"
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module DhanHQ
|
|
4
|
-
module Utils
|
|
5
|
-
# Helper utilities for working with DhanHQ instruments
|
|
6
|
-
class InstrumentHelper
|
|
7
|
-
def self.safe_attr(instrument, attr_name)
|
|
8
|
-
return nil unless instrument
|
|
9
|
-
|
|
10
|
-
instrument.respond_to?(attr_name) ? instrument.send(attr_name) : nil
|
|
11
|
-
rescue StandardError
|
|
12
|
-
nil
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def self.extract_value(data, keys)
|
|
16
|
-
return nil unless data
|
|
17
|
-
|
|
18
|
-
keys.each do |key|
|
|
19
|
-
if data.is_a?(Hash)
|
|
20
|
-
return data[key] if data.key?(key)
|
|
21
|
-
elsif data.respond_to?(key)
|
|
22
|
-
return data.send(key)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
return data if data.is_a?(Numeric) || data.is_a?(String)
|
|
27
|
-
|
|
28
|
-
nil
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module DhanHQ
|
|
4
|
-
module Utils
|
|
5
|
-
# Cleans parameters from LLM responses (removes comments, instructions, etc.)
|
|
6
|
-
class ParameterCleaner
|
|
7
|
-
MAX_KEY_LENGTH = 50
|
|
8
|
-
|
|
9
|
-
def self.clean(params)
|
|
10
|
-
return {} unless params.is_a?(Hash)
|
|
11
|
-
|
|
12
|
-
params.reject do |key, _value|
|
|
13
|
-
key_str = key.to_s
|
|
14
|
-
invalid_key?(key_str)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def self.invalid_key?(key_str)
|
|
19
|
-
key_str.start_with?(">") ||
|
|
20
|
-
key_str.start_with?("//") ||
|
|
21
|
-
key_str.include?("adjust") ||
|
|
22
|
-
key_str.length > MAX_KEY_LENGTH
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private_class_method :invalid_key?
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module DhanHQ
|
|
6
|
-
module Utils
|
|
7
|
-
# Normalizes parameters from LLM responses (handles arrays, stringified JSON, etc.)
|
|
8
|
-
class ParameterNormalizer
|
|
9
|
-
STRING_FIELDS = %w[symbol exchange_segment].freeze
|
|
10
|
-
|
|
11
|
-
def self.normalize(params)
|
|
12
|
-
return {} unless params.is_a?(Hash)
|
|
13
|
-
|
|
14
|
-
params.each_with_object({}) do |(key, value), normalized|
|
|
15
|
-
normalized[key] = if STRING_FIELDS.include?(key.to_s)
|
|
16
|
-
normalize_string_field(value)
|
|
17
|
-
else
|
|
18
|
-
value
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.normalize_string_field(value)
|
|
24
|
-
return value.to_s if value.nil?
|
|
25
|
-
|
|
26
|
-
if value.is_a?(Array) && !value.empty?
|
|
27
|
-
value.first.to_s
|
|
28
|
-
elsif value.is_a?(String) && value.strip.start_with?("[") && value.strip.end_with?("]")
|
|
29
|
-
parse_stringified_array(value)
|
|
30
|
-
else
|
|
31
|
-
value.to_s
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def self.parse_stringified_array(value)
|
|
36
|
-
parsed = JSON.parse(value)
|
|
37
|
-
parsed.is_a?(Array) && !parsed.empty? ? parsed.first.to_s : value.to_s
|
|
38
|
-
rescue JSON::ParserError
|
|
39
|
-
value.to_s
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private_class_method :normalize_string_field, :parse_stringified_array
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module DhanHQ
|
|
4
|
-
module Utils
|
|
5
|
-
# Rate limiter for DhanHQ MarketFeed APIs (1 request per second)
|
|
6
|
-
class RateLimiter
|
|
7
|
-
MARKETFEED_INTERVAL = 1.1 # 1 second + 0.1s buffer
|
|
8
|
-
|
|
9
|
-
@last_marketfeed_call = nil
|
|
10
|
-
@marketfeed_mutex = Mutex.new
|
|
11
|
-
|
|
12
|
-
def self.marketfeed
|
|
13
|
-
@marketfeed_mutex.synchronize do
|
|
14
|
-
if @last_marketfeed_call
|
|
15
|
-
elapsed = Time.now - @last_marketfeed_call
|
|
16
|
-
sleep(MARKETFEED_INTERVAL - elapsed) if elapsed < MARKETFEED_INTERVAL
|
|
17
|
-
end
|
|
18
|
-
@last_marketfeed_call = Time.now
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module DhanHQ
|
|
4
|
-
module Utils
|
|
5
|
-
# Normalizes trading parameters from LLM responses
|
|
6
|
-
# Handles common issues like comma-separated numbers, string numbers, etc.
|
|
7
|
-
class TradingParameterNormalizer
|
|
8
|
-
NUMERIC_FIELDS = %w[price target_price stop_loss_price quantity trailing_jump].freeze
|
|
9
|
-
|
|
10
|
-
def self.normalize(params)
|
|
11
|
-
return {} unless params.is_a?(Hash)
|
|
12
|
-
|
|
13
|
-
params.each_with_object({}) do |(key, value), normalized|
|
|
14
|
-
key_str = key.to_s
|
|
15
|
-
normalized[key] = if NUMERIC_FIELDS.include?(key_str)
|
|
16
|
-
normalize_numeric(value)
|
|
17
|
-
elsif key_str == "security_id"
|
|
18
|
-
normalize_security_id(value)
|
|
19
|
-
else
|
|
20
|
-
value
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def self.normalize_numeric(value)
|
|
26
|
-
return nil if value.nil?
|
|
27
|
-
|
|
28
|
-
# If already a number, return as-is (but validate it's reasonable)
|
|
29
|
-
return value if value.is_a?(Numeric)
|
|
30
|
-
|
|
31
|
-
# If string, clean and convert
|
|
32
|
-
return nil unless value.is_a?(String)
|
|
33
|
-
|
|
34
|
-
# Remove commas, spaces, currency symbols, and convert
|
|
35
|
-
cleaned = value.to_s.gsub(/[,\s$₹]/, "")
|
|
36
|
-
return nil if cleaned.empty?
|
|
37
|
-
|
|
38
|
-
# Try to convert to float (for prices) or integer (for quantity)
|
|
39
|
-
if cleaned.include?(".")
|
|
40
|
-
cleaned.to_f
|
|
41
|
-
else
|
|
42
|
-
cleaned.to_i
|
|
43
|
-
end
|
|
44
|
-
rescue StandardError
|
|
45
|
-
nil
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Validates if a price value seems reasonable (not obviously wrong)
|
|
49
|
-
def self.valid_price?(price, context_hint = nil)
|
|
50
|
-
return false if price.nil?
|
|
51
|
-
|
|
52
|
-
# If price is suspiciously low (< 10), it might be wrong
|
|
53
|
-
# But we can't be too strict since some stocks might legitimately be < 10
|
|
54
|
-
# Check if context hint suggests a higher price
|
|
55
|
-
if price.is_a?(Numeric) && price.positive? && price < 10 &&
|
|
56
|
-
context_hint.is_a?(Numeric) && context_hint > price * 100
|
|
57
|
-
return false # Likely wrong
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
true
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def self.normalize_security_id(value)
|
|
64
|
-
return nil if value.nil?
|
|
65
|
-
|
|
66
|
-
value.to_s
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private_class_method :normalize_numeric, :normalize_security_id, :valid_price?
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|