ollama_agent 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.cursor/skills/ruby-code-review-levels/SKILL.md +115 -0
- data/.cursor/skills/self-improvement-sandbox-safety/SKILL.md +65 -0
- data/.env.example +25 -0
- data/CHANGELOG.md +40 -0
- data/README.md +135 -4
- data/docs/ARCHITECTURE.md +42 -0
- data/docs/PERFORMANCE.md +22 -0
- data/docs/SESSIONS.md +48 -0
- data/docs/TOOLS.md +53 -0
- data/docs/TOOL_RUNTIME.md +154 -0
- data/docs/superpowers/plans/2026-03-26-production-ready-ollama-agent.md +2454 -0
- data/docs/superpowers/specs/2026-03-26-production-ready-ollama-agent-design.md +400 -0
- data/lib/ollama_agent/agent/agent_config.rb +53 -0
- data/lib/ollama_agent/agent/client_wiring.rb +76 -0
- data/lib/ollama_agent/agent/prompt_wiring.rb +55 -0
- data/lib/ollama_agent/agent/session_wiring.rb +53 -0
- data/lib/ollama_agent/agent.rb +148 -73
- data/lib/ollama_agent/agent_prompt.rb +31 -1
- data/lib/ollama_agent/chat_stream_carry.rb +88 -0
- data/lib/ollama_agent/chat_stream_thinking_format.rb +29 -0
- data/lib/ollama_agent/cli.rb +394 -4
- data/lib/ollama_agent/console.rb +177 -5
- data/lib/ollama_agent/context/manager.rb +100 -0
- data/lib/ollama_agent/context/token_counter.rb +33 -0
- data/lib/ollama_agent/diff_path_validator.rb +32 -10
- data/lib/ollama_agent/env_config.rb +44 -0
- data/lib/ollama_agent/external_agents/TODO-plan.md +1948 -0
- data/lib/ollama_agent/external_agents/argv_interp.rb +21 -0
- data/lib/ollama_agent/external_agents/default_agents.yml +60 -0
- data/lib/ollama_agent/external_agents/delegate_logger.rb +31 -0
- data/lib/ollama_agent/external_agents/delegate_timeout_status.rb +12 -0
- data/lib/ollama_agent/external_agents/env_helpers.rb +38 -0
- data/lib/ollama_agent/external_agents/path_validator.rb +32 -0
- data/lib/ollama_agent/external_agents/probe.rb +122 -0
- data/lib/ollama_agent/external_agents/registry.rb +50 -0
- data/lib/ollama_agent/external_agents/runner.rb +118 -0
- data/lib/ollama_agent/external_agents.rb +9 -0
- data/lib/ollama_agent/global_dotenv.rb +39 -0
- data/lib/ollama_agent/model_env.rb +26 -0
- data/lib/ollama_agent/ollama_chat_thinking_stream.rb +41 -0
- data/lib/ollama_agent/ollama_connection.rb +6 -1
- data/lib/ollama_agent/patch_risk.rb +81 -0
- data/lib/ollama_agent/patch_support.rb +27 -1
- data/lib/ollama_agent/path_sandbox.rb +62 -0
- data/lib/ollama_agent/prompt_skills/clean_ruby.md +131 -0
- data/lib/ollama_agent/prompt_skills/code_review.md +112 -0
- data/lib/ollama_agent/prompt_skills/design_patterns.md +56 -0
- data/lib/ollama_agent/prompt_skills/manifest.yml +25 -0
- data/lib/ollama_agent/prompt_skills/ollama_agent_patterns.md +132 -0
- data/lib/ollama_agent/prompt_skills/rails_best_practices.md +41 -0
- data/lib/ollama_agent/prompt_skills/rails_style.md +138 -0
- data/lib/ollama_agent/prompt_skills/rspec.md +280 -0
- data/lib/ollama_agent/prompt_skills/rubocop.md +7 -0
- data/lib/ollama_agent/prompt_skills/ruby_style.md +121 -0
- data/lib/ollama_agent/prompt_skills/solid.md +270 -0
- data/lib/ollama_agent/prompt_skills/solid_ruby.md +223 -0
- data/lib/ollama_agent/prompt_skills.rb +169 -0
- data/lib/ollama_agent/repo_list.rb +4 -1
- data/lib/ollama_agent/resilience/audit_logger.rb +79 -0
- data/lib/ollama_agent/resilience/retry_middleware.rb +45 -0
- data/lib/ollama_agent/resilience/retry_policy.rb +51 -0
- data/lib/ollama_agent/ruby_index_tool_support.rb +17 -6
- data/lib/ollama_agent/runner.rb +123 -0
- data/lib/ollama_agent/sandboxed_tools/delegate_external.rb +62 -0
- data/lib/ollama_agent/sandboxed_tools/file_read_write.rb +100 -0
- data/lib/ollama_agent/sandboxed_tools/search_text.rb +60 -0
- data/lib/ollama_agent/sandboxed_tools.rb +55 -116
- data/lib/ollama_agent/search_backend.rb +93 -0
- data/lib/ollama_agent/self_improvement/analyzer.rb +34 -0
- data/lib/ollama_agent/self_improvement/improver.rb +340 -0
- data/lib/ollama_agent/self_improvement/modes.rb +25 -0
- data/lib/ollama_agent/self_improvement/ruby_mastery_context.rb +66 -0
- data/lib/ollama_agent/self_improvement.rb +5 -0
- data/lib/ollama_agent/session/session.rb +8 -0
- data/lib/ollama_agent/session/store.rb +68 -0
- data/lib/ollama_agent/streaming/console_streamer.rb +29 -0
- data/lib/ollama_agent/streaming/hooks.rb +39 -0
- data/lib/ollama_agent/tool_arguments.rb +13 -1
- data/lib/ollama_agent/tool_content_parser.rb +1 -1
- data/lib/ollama_agent/tool_runtime/executor.rb +34 -0
- data/lib/ollama_agent/tool_runtime/json_extractor.rb +62 -0
- data/lib/ollama_agent/tool_runtime/loop.rb +72 -0
- data/lib/ollama_agent/tool_runtime/memory.rb +32 -0
- data/lib/ollama_agent/tool_runtime/ollama_json_planner.rb +98 -0
- data/lib/ollama_agent/tool_runtime/plan_extractor.rb +12 -0
- data/lib/ollama_agent/tool_runtime/registry.rb +60 -0
- data/lib/ollama_agent/tool_runtime/tool.rb +24 -0
- data/lib/ollama_agent/tool_runtime.rb +24 -0
- data/lib/ollama_agent/tools/registry.rb +55 -0
- data/lib/ollama_agent/tools_schema.rb +74 -1
- data/lib/ollama_agent/user_prompt.rb +35 -0
- data/lib/ollama_agent/version.rb +1 -1
- data/lib/ollama_agent.rb +25 -0
- data/reproduce_429.rb +40 -0
- data/sig/ollama_agent.rbs +111 -1
- metadata +78 -2
|
@@ -0,0 +1,1948 @@
|
|
|
1
|
+
Out of these what can we take for our ollama-agent gem
|
|
2
|
+
|
|
3
|
+
Direct assessment: this gist by Andrej Karpathy is one of the most important conceptual blueprints for what you’re trying to build (agentic trading + codegen system). It is not “code to copy”—it’s an execution philosophy.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
What this gist actually is (no fluff)
|
|
9
|
+
|
|
10
|
+
It defines a minimal viable agent loop:
|
|
11
|
+
|
|
12
|
+
while True:
|
|
13
|
+
think
|
|
14
|
+
act (tool use)
|
|
15
|
+
observe
|
|
16
|
+
|
|
17
|
+
This is the core runtime primitive behind:
|
|
18
|
+
|
|
19
|
+
Cursor agents
|
|
20
|
+
|
|
21
|
+
Claude Code
|
|
22
|
+
|
|
23
|
+
OpenAI tool agents
|
|
24
|
+
|
|
25
|
+
Your Ollama agent (currently underpowered vs this model)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
Correct interpretation (for your system)
|
|
32
|
+
|
|
33
|
+
You are NOT building:
|
|
34
|
+
|
|
35
|
+
chatbot ❌
|
|
36
|
+
|
|
37
|
+
prompt wrapper ❌
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
You are building:
|
|
41
|
+
|
|
42
|
+
> deterministic, tool-driven execution engine with LLM as planner
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
Core primitives extracted
|
|
50
|
+
|
|
51
|
+
1. The Agent Loop (non-negotiable)
|
|
52
|
+
|
|
53
|
+
loop do
|
|
54
|
+
thought = planner.next_step(context)
|
|
55
|
+
|
|
56
|
+
action = tool_selector.resolve(thought)
|
|
57
|
+
|
|
58
|
+
result = action.execute
|
|
59
|
+
|
|
60
|
+
memory.append(thought:, action:, result:)
|
|
61
|
+
|
|
62
|
+
break if termination?(result)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
In your trading system:
|
|
66
|
+
|
|
67
|
+
Step Meaning
|
|
68
|
+
|
|
69
|
+
think "find CE opportunity on NIFTY using SMC + RSI"
|
|
70
|
+
act call DhanHQ + indicator engine
|
|
71
|
+
observe market data + signal validity
|
|
72
|
+
memory store trade attempt + result
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
2. Tools are the REAL system
|
|
79
|
+
|
|
80
|
+
Karpathy emphasizes:
|
|
81
|
+
|
|
82
|
+
> LLM is useless without tools
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
For you:
|
|
87
|
+
|
|
88
|
+
Mandatory tools (non-negotiable)
|
|
89
|
+
|
|
90
|
+
TOOLS = [
|
|
91
|
+
:fetch_option_chain,
|
|
92
|
+
:fetch_ltp,
|
|
93
|
+
:fetch_historical_candles,
|
|
94
|
+
:calculate_indicators,
|
|
95
|
+
:evaluate_strategy,
|
|
96
|
+
:backtest_strategy,
|
|
97
|
+
:place_order,
|
|
98
|
+
:modify_order,
|
|
99
|
+
:exit_position,
|
|
100
|
+
:log_trade,
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
If these are weak → your agent is useless.
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
3. Text is the interface (critical insight)
|
|
109
|
+
|
|
110
|
+
Everything becomes:
|
|
111
|
+
|
|
112
|
+
tool input → string
|
|
113
|
+
|
|
114
|
+
tool output → string
|
|
115
|
+
|
|
116
|
+
reasoning → string
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
That means:
|
|
120
|
+
|
|
121
|
+
Your DhanHQ wrapper must support:
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
"tool": "fetch_option_chain",
|
|
125
|
+
"args": {
|
|
126
|
+
"symbol": "NIFTY",
|
|
127
|
+
"expiry": "2026-04-09"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
NOT raw Ruby calls.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
4. Memory design (where most systems fail)
|
|
137
|
+
|
|
138
|
+
Karpathy implicitly separates:
|
|
139
|
+
|
|
140
|
+
Short-term memory
|
|
141
|
+
|
|
142
|
+
current reasoning loop
|
|
143
|
+
|
|
144
|
+
last tool results
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
Long-term memory (YOU MUST BUILD THIS)
|
|
148
|
+
|
|
149
|
+
For your case:
|
|
150
|
+
|
|
151
|
+
class StrategyMemory
|
|
152
|
+
def store(strategy:, pnl:, win_rate:, conditions:)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def best_strategies(context:)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
Without this → your agent will never improve.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
5. Determinism vs hallucination
|
|
165
|
+
|
|
166
|
+
Karpathy’s implicit rule:
|
|
167
|
+
|
|
168
|
+
> LLM decides WHAT, system decides HOW
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
Correct architecture (for you)
|
|
173
|
+
|
|
174
|
+
LLM:
|
|
175
|
+
- selects strategy
|
|
176
|
+
- decides next action
|
|
177
|
+
|
|
178
|
+
Your system:
|
|
179
|
+
- validates inputs
|
|
180
|
+
- executes trades
|
|
181
|
+
- enforces risk
|
|
182
|
+
|
|
183
|
+
NEVER allow:
|
|
184
|
+
|
|
185
|
+
# ❌ WRONG
|
|
186
|
+
llm.place_order_directly
|
|
187
|
+
|
|
188
|
+
# ✅ CORRECT
|
|
189
|
+
risk_manager.validate!
|
|
190
|
+
order_executor.execute!
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
How this maps to YOUR system
|
|
196
|
+
|
|
197
|
+
You are building:
|
|
198
|
+
|
|
199
|
+
> Autonomous Options Strategy Research + Execution Engine
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
Final architecture (production-grade)
|
|
207
|
+
|
|
208
|
+
Agent Runtime
|
|
209
|
+
├── Planner (LLM / Ollama / GPT)
|
|
210
|
+
├── Tool Registry
|
|
211
|
+
├── Execution Engine
|
|
212
|
+
├── Memory System
|
|
213
|
+
└── Risk Engine
|
|
214
|
+
|
|
215
|
+
Domain Layer (Rails)
|
|
216
|
+
├── Instrument
|
|
217
|
+
├── CandleSeries
|
|
218
|
+
├── Indicators
|
|
219
|
+
├── Strategy DSL
|
|
220
|
+
└── Backtester
|
|
221
|
+
|
|
222
|
+
Infra Layer
|
|
223
|
+
├── DhanHQ Client
|
|
224
|
+
├── WebSocket Feed
|
|
225
|
+
├── Cache (LTP)
|
|
226
|
+
└── Job Queue
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
Critical design mistakes you are currently at risk of
|
|
232
|
+
|
|
233
|
+
1. Over-LLM-ing the system ❌
|
|
234
|
+
|
|
235
|
+
If you let LLM:
|
|
236
|
+
|
|
237
|
+
pick strike
|
|
238
|
+
|
|
239
|
+
pick SL
|
|
240
|
+
|
|
241
|
+
pick quantity
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
→ you will lose money
|
|
245
|
+
|
|
246
|
+
Fix:
|
|
247
|
+
|
|
248
|
+
Hardcode risk logic:
|
|
249
|
+
|
|
250
|
+
class RiskManager
|
|
251
|
+
def position_size(capital:, risk_per_trade:)
|
|
252
|
+
(capital * risk_per_trade) / stop_loss
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
2. No evaluation loop ❌
|
|
260
|
+
|
|
261
|
+
If you don’t do:
|
|
262
|
+
|
|
263
|
+
generate → backtest → rank → deploy
|
|
264
|
+
|
|
265
|
+
You are gambling, not building a system.
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
3. Weak tool abstraction ❌
|
|
271
|
+
|
|
272
|
+
If your tools are:
|
|
273
|
+
|
|
274
|
+
def fetch_data
|
|
275
|
+
|
|
276
|
+
Instead of:
|
|
277
|
+
|
|
278
|
+
def fetch_option_chain(symbol:, expiry:)
|
|
279
|
+
|
|
280
|
+
Agent will fail.
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
What you should build next (no debate)
|
|
286
|
+
|
|
287
|
+
Step 1 — Tool interface layer
|
|
288
|
+
|
|
289
|
+
class AgentTool
|
|
290
|
+
def name; end
|
|
291
|
+
def schema; end
|
|
292
|
+
def call(args); end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
Step 2 — Strategy generator
|
|
299
|
+
|
|
300
|
+
LLM outputs:
|
|
301
|
+
|
|
302
|
+
{
|
|
303
|
+
"strategy": {
|
|
304
|
+
"entry": "RSI < 30 AND price near OB",
|
|
305
|
+
"exit": "RR 1:2 OR SL hit",
|
|
306
|
+
"instrument": "NIFTY CE"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
Step 3 — Backtesting engine (MANDATORY)
|
|
314
|
+
|
|
315
|
+
Use:
|
|
316
|
+
|
|
317
|
+
DhanHQ historical API
|
|
318
|
+
|
|
319
|
+
your CandleSeries
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
Step 4 — Strategy scoring
|
|
326
|
+
|
|
327
|
+
score = (
|
|
328
|
+
win_rate * 0.4 +
|
|
329
|
+
profit_factor * 0.4 +
|
|
330
|
+
drawdown_penalty * -0.2
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
Step 5 — Execution bridge
|
|
337
|
+
|
|
338
|
+
Only top strategies go live.
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
Final verdict
|
|
344
|
+
|
|
345
|
+
This gist is:
|
|
346
|
+
|
|
347
|
+
> The operating system of agents
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
If you implement it properly:
|
|
352
|
+
|
|
353
|
+
You get self-improving trading system
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
If you misuse it:
|
|
357
|
+
|
|
358
|
+
You get a random signal generator with LLM noise
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
If you want next step
|
|
365
|
+
|
|
366
|
+
I’ll build for you:
|
|
367
|
+
|
|
368
|
+
→ Full Ruby implementation of:
|
|
369
|
+
|
|
370
|
+
Agent loop
|
|
371
|
+
|
|
372
|
+
Tool system
|
|
373
|
+
|
|
374
|
+
Strategy generator
|
|
375
|
+
|
|
376
|
+
Backtester integration (DhanHQ)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
Say:
|
|
380
|
+
|
|
381
|
+
> "build agent runtime v1"
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
And I’ll deliver production-grade code (no placeholders).
|
|
386
|
+
|
|
387
|
+
https://github.com/shubhamtaywade82/ollama_agent
|
|
388
|
+
|
|
389
|
+
Direct decision:
|
|
390
|
+
|
|
391
|
+
You should extract only 5 primitives from Andrej Karpathy’s gist and embed them as first-class constructs inside your ollama_agent gem.
|
|
392
|
+
|
|
393
|
+
Everything else is secondary.
|
|
394
|
+
|
|
395
|
+
What goes into your gem (non-negotiable core)
|
|
396
|
+
1. Agent Runtime Loop (THE backbone)
|
|
397
|
+
|
|
398
|
+
This is not optional. This becomes your gem’s core engine.
|
|
399
|
+
|
|
400
|
+
Implementation (production-ready)
|
|
401
|
+
module OllamaAgent
|
|
402
|
+
class Runner
|
|
403
|
+
MAX_STEPS = 10
|
|
404
|
+
|
|
405
|
+
def initialize(planner:, tool_registry:, memory:, executor:, logger:)
|
|
406
|
+
@planner = planner
|
|
407
|
+
@tool_registry = tool_registry
|
|
408
|
+
@memory = memory
|
|
409
|
+
@executor = executor
|
|
410
|
+
@logger = logger
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def run(context:)
|
|
414
|
+
steps = 0
|
|
415
|
+
|
|
416
|
+
loop do
|
|
417
|
+
raise "Max steps exceeded" if steps >= MAX_STEPS
|
|
418
|
+
|
|
419
|
+
thought = @planner.next_step(context: context, memory: @memory)
|
|
420
|
+
|
|
421
|
+
@logger.info("THOUGHT: #{thought}")
|
|
422
|
+
|
|
423
|
+
action = @tool_registry.resolve(thought)
|
|
424
|
+
|
|
425
|
+
raise "Unknown tool: #{thought}" unless action
|
|
426
|
+
|
|
427
|
+
result = @executor.execute(action)
|
|
428
|
+
|
|
429
|
+
@logger.info("ACTION: #{action[:tool]} RESULT: #{result}")
|
|
430
|
+
|
|
431
|
+
@memory.append(thought:, action:, result:)
|
|
432
|
+
|
|
433
|
+
break if termination?(result)
|
|
434
|
+
|
|
435
|
+
steps += 1
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
private
|
|
440
|
+
|
|
441
|
+
def termination?(result)
|
|
442
|
+
result[:status] == "done"
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
2. Tool System (REAL power layer)
|
|
447
|
+
|
|
448
|
+
Your current system likely treats tools loosely. That’s a mistake.
|
|
449
|
+
|
|
450
|
+
Required abstraction
|
|
451
|
+
module OllamaAgent
|
|
452
|
+
class Tool
|
|
453
|
+
def name
|
|
454
|
+
raise NotImplementedError
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def schema
|
|
458
|
+
raise NotImplementedError
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def call(args)
|
|
462
|
+
raise NotImplementedError
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
Tool Registry
|
|
467
|
+
module OllamaAgent
|
|
468
|
+
class ToolRegistry
|
|
469
|
+
def initialize(tools)
|
|
470
|
+
@tools = tools.index_by(&:name)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def resolve(thought)
|
|
474
|
+
parsed = JSON.parse(thought) rescue nil
|
|
475
|
+
return nil unless parsed
|
|
476
|
+
|
|
477
|
+
tool = @tools[parsed["tool"]]
|
|
478
|
+
return nil unless tool
|
|
479
|
+
|
|
480
|
+
{
|
|
481
|
+
tool: tool,
|
|
482
|
+
args: parsed["args"]
|
|
483
|
+
}
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
3. Planner (LLM adapter — Ollama)
|
|
488
|
+
|
|
489
|
+
This is where your gem integrates with Ollama.
|
|
490
|
+
|
|
491
|
+
Critical constraint:
|
|
492
|
+
Must output STRICT JSON
|
|
493
|
+
No free text
|
|
494
|
+
module OllamaAgent
|
|
495
|
+
class Planner
|
|
496
|
+
def initialize(client:, model:)
|
|
497
|
+
@client = client
|
|
498
|
+
@model = model
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def next_step(context:, memory:)
|
|
502
|
+
prompt = build_prompt(context, memory)
|
|
503
|
+
|
|
504
|
+
response = @client.generate(
|
|
505
|
+
model: @model,
|
|
506
|
+
prompt: prompt
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
extract_json(response)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
private
|
|
513
|
+
|
|
514
|
+
def build_prompt(context, memory)
|
|
515
|
+
<<~PROMPT
|
|
516
|
+
You are an agent. Respond ONLY in JSON.
|
|
517
|
+
|
|
518
|
+
Available tools:
|
|
519
|
+
#{memory.tool_descriptions}
|
|
520
|
+
|
|
521
|
+
Context:
|
|
522
|
+
#{context}
|
|
523
|
+
|
|
524
|
+
Memory:
|
|
525
|
+
#{memory.recent}
|
|
526
|
+
|
|
527
|
+
Output format:
|
|
528
|
+
{
|
|
529
|
+
"tool": "tool_name",
|
|
530
|
+
"args": { }
|
|
531
|
+
}
|
|
532
|
+
PROMPT
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def extract_json(response)
|
|
536
|
+
response[/\{.*\}/m] || raise("Invalid LLM response")
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
4. Memory System (THIS is where edge comes from)
|
|
541
|
+
|
|
542
|
+
Split explicitly.
|
|
543
|
+
|
|
544
|
+
Short-term memory
|
|
545
|
+
module OllamaAgent
|
|
546
|
+
class Memory
|
|
547
|
+
def initialize
|
|
548
|
+
@steps = []
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def append(thought:, action:, result:)
|
|
552
|
+
@steps << { thought:, action:, result: }
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
def recent(limit = 5)
|
|
556
|
+
@steps.last(limit)
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def tool_descriptions
|
|
560
|
+
# injected externally
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
Long-term memory (strategy learning)
|
|
565
|
+
|
|
566
|
+
This should NOT be inside agent runtime. Keep it pluggable.
|
|
567
|
+
|
|
568
|
+
class StrategyMemory
|
|
569
|
+
def store(strategy:, pnl:, win_rate:, metadata:)
|
|
570
|
+
StrategyRecord.create!(...)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def top_strategies(context:)
|
|
574
|
+
StrategyRecord.where(context: context).order(score: :desc).limit(5)
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
5. Execution Boundary (hard separation)
|
|
578
|
+
|
|
579
|
+
Never allow tool to directly mutate critical systems.
|
|
580
|
+
|
|
581
|
+
module OllamaAgent
|
|
582
|
+
class Executor
|
|
583
|
+
def initialize(risk_manager:)
|
|
584
|
+
@risk_manager = risk_manager
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def execute(action)
|
|
588
|
+
tool = action[:tool]
|
|
589
|
+
args = action[:args]
|
|
590
|
+
|
|
591
|
+
validated_args = @risk_manager.validate(tool.name, args)
|
|
592
|
+
|
|
593
|
+
tool.call(validated_args)
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
What you DO NOT include in the gem
|
|
598
|
+
|
|
599
|
+
Be strict here.
|
|
600
|
+
|
|
601
|
+
❌ No trading logic
|
|
602
|
+
No strike selection
|
|
603
|
+
No SL/TP logic
|
|
604
|
+
No DhanHQ-specific logic
|
|
605
|
+
|
|
606
|
+
These belong to:
|
|
607
|
+
→ your AlgoTradingApi app
|
|
608
|
+
|
|
609
|
+
❌ No strategy intelligence
|
|
610
|
+
No SMC logic
|
|
611
|
+
No RSI logic
|
|
612
|
+
|
|
613
|
+
Agent should CALL tools, not implement domain logic.
|
|
614
|
+
|
|
615
|
+
❌ No backtesting engine
|
|
616
|
+
|
|
617
|
+
Keep it external tool:
|
|
618
|
+
|
|
619
|
+
BacktestStrategyTool < Tool
|
|
620
|
+
What becomes plugins (important)
|
|
621
|
+
|
|
622
|
+
Your gem should be:
|
|
623
|
+
|
|
624
|
+
agent runtime framework
|
|
625
|
+
|
|
626
|
+
Everything else = plugin.
|
|
627
|
+
|
|
628
|
+
Example plugin set (your trading app)
|
|
629
|
+
tools = [
|
|
630
|
+
Tools::FetchOptionChain.new,
|
|
631
|
+
Tools::Indicators.new,
|
|
632
|
+
Tools::Backtester.new,
|
|
633
|
+
Tools::OrderExecutor.new
|
|
634
|
+
]
|
|
635
|
+
Critical risks (callout)
|
|
636
|
+
1. JSON brittleness (biggest failure point)
|
|
637
|
+
|
|
638
|
+
LLM WILL break format.
|
|
639
|
+
|
|
640
|
+
Fix:
|
|
641
|
+
|
|
642
|
+
def extract_json(response)
|
|
643
|
+
json = response[/\{.*\}/m]
|
|
644
|
+
raise "No JSON found" unless json
|
|
645
|
+
|
|
646
|
+
JSON.parse(json)
|
|
647
|
+
rescue JSON::ParserError
|
|
648
|
+
raise "Invalid JSON from LLM: #{response}"
|
|
649
|
+
end
|
|
650
|
+
2. Infinite loops
|
|
651
|
+
|
|
652
|
+
Already handled with:
|
|
653
|
+
|
|
654
|
+
MAX_STEPS = 10
|
|
655
|
+
|
|
656
|
+
Do not remove this.
|
|
657
|
+
|
|
658
|
+
3. Tool hallucination
|
|
659
|
+
|
|
660
|
+
LLM will invent tools.
|
|
661
|
+
|
|
662
|
+
Fix already in:
|
|
663
|
+
|
|
664
|
+
@tools[parsed["tool"]]
|
|
665
|
+
|
|
666
|
+
Reject unknown tools.
|
|
667
|
+
|
|
668
|
+
Minimal gem structure (correct)
|
|
669
|
+
ollama_agent/
|
|
670
|
+
lib/
|
|
671
|
+
ollama_agent/
|
|
672
|
+
runner.rb
|
|
673
|
+
planner.rb
|
|
674
|
+
tool.rb
|
|
675
|
+
tool_registry.rb
|
|
676
|
+
executor.rb
|
|
677
|
+
memory.rb
|
|
678
|
+
|
|
679
|
+
That’s it.
|
|
680
|
+
|
|
681
|
+
If your gem grows beyond this → you’re overengineering.
|
|
682
|
+
|
|
683
|
+
Final verdict
|
|
684
|
+
|
|
685
|
+
Take ONLY:
|
|
686
|
+
|
|
687
|
+
Concept Include
|
|
688
|
+
Agent loop ✅
|
|
689
|
+
Tool abstraction ✅
|
|
690
|
+
Planner (LLM adapter) ✅
|
|
691
|
+
Memory (short-term) ✅
|
|
692
|
+
Execution boundary ✅
|
|
693
|
+
|
|
694
|
+
Everything else:
|
|
695
|
+
→ belongs to your trading system, not the gem.
|
|
696
|
+
|
|
697
|
+
Next step (recommended)
|
|
698
|
+
|
|
699
|
+
You are now ready for:
|
|
700
|
+
|
|
701
|
+
"agent-runtime v1 + trading tools integration"
|
|
702
|
+
|
|
703
|
+
If you want, I’ll:
|
|
704
|
+
|
|
705
|
+
Refactor your existing ollama_agent repo
|
|
706
|
+
Add strict JSON tool calling
|
|
707
|
+
Integrate with your DhanHQ stack
|
|
708
|
+
Add backtesting loop (generate → test → rank)
|
|
709
|
+
|
|
710
|
+
Say:
|
|
711
|
+
|
|
712
|
+
“refactor my ollama_agent repo”
|
|
713
|
+
|
|
714
|
+
and I’ll do a full rewrite aligned to this architecture.
|
|
715
|
+
|
|
716
|
+
Keep the trading agent as seperate which we can integrate with ollama-agent not in ollama-agent
|
|
717
|
+
|
|
718
|
+
Direct decision: Correct. Lock this as a hard architectural boundary.
|
|
719
|
+
|
|
720
|
+
Your ollama_agent gem becomes a pure agent runtime framework.
|
|
721
|
+
Your trading system becomes a tool/plugin ecosystem that plugs into it.
|
|
722
|
+
|
|
723
|
+
Anything else will collapse under complexity.
|
|
724
|
+
|
|
725
|
+
Final Architecture (clean separation)
|
|
726
|
+
1. ollama_agent (framework — reusable, generic)
|
|
727
|
+
|
|
728
|
+
Responsibility:
|
|
729
|
+
|
|
730
|
+
Agent loop
|
|
731
|
+
Tool execution protocol
|
|
732
|
+
LLM planning
|
|
733
|
+
Memory (short-term)
|
|
734
|
+
Execution safety boundary
|
|
735
|
+
|
|
736
|
+
No domain logic. Zero.
|
|
737
|
+
|
|
738
|
+
2. algo_trading_agent (your system — domain layer)
|
|
739
|
+
|
|
740
|
+
Responsibility:
|
|
741
|
+
|
|
742
|
+
DhanHQ integration
|
|
743
|
+
Indicators (RSI, SMC, etc.)
|
|
744
|
+
Strategy generation + evaluation
|
|
745
|
+
Backtesting
|
|
746
|
+
Risk management
|
|
747
|
+
Order execution
|
|
748
|
+
3. Integration Layer (glue)
|
|
749
|
+
agent = OllamaAgent::Runner.new(
|
|
750
|
+
planner: OllamaAgent::Planner.new(client: ollama_client, model: "llama3"),
|
|
751
|
+
tool_registry: OllamaAgent::ToolRegistry.new(trading_tools),
|
|
752
|
+
memory: OllamaAgent::Memory.new,
|
|
753
|
+
executor: OllamaAgent::Executor.new(risk_manager: Trading::RiskManager.new),
|
|
754
|
+
logger: Rails.logger
|
|
755
|
+
)
|
|
756
|
+
What goes into ollama_agent (strict scope)
|
|
757
|
+
Core modules
|
|
758
|
+
ollama_agent/
|
|
759
|
+
runner.rb
|
|
760
|
+
planner.rb
|
|
761
|
+
tool.rb
|
|
762
|
+
tool_registry.rb
|
|
763
|
+
executor.rb
|
|
764
|
+
memory.rb
|
|
765
|
+
Strict Tool Contract
|
|
766
|
+
module OllamaAgent
|
|
767
|
+
class Tool
|
|
768
|
+
def name; end
|
|
769
|
+
def description; end
|
|
770
|
+
def schema; end
|
|
771
|
+
def call(args); end
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
This is your plugin API.
|
|
776
|
+
|
|
777
|
+
What goes into algo_trading_agent
|
|
778
|
+
|
|
779
|
+
Everything that touches markets.
|
|
780
|
+
|
|
781
|
+
Example: Tool implementation (trading side)
|
|
782
|
+
module Trading
|
|
783
|
+
module Tools
|
|
784
|
+
class FetchOptionChain < OllamaAgent::Tool
|
|
785
|
+
def name
|
|
786
|
+
"fetch_option_chain"
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
def description
|
|
790
|
+
"Fetch option chain for a given index and expiry"
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
def schema
|
|
794
|
+
{
|
|
795
|
+
symbol: "string",
|
|
796
|
+
expiry: "string"
|
|
797
|
+
}
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
def call(args)
|
|
801
|
+
client = Dhanhq::Client.new
|
|
802
|
+
|
|
803
|
+
data = client.option_chain(
|
|
804
|
+
symbol: args["symbol"],
|
|
805
|
+
expiry: args["expiry"]
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
{
|
|
809
|
+
status: "success",
|
|
810
|
+
data: data
|
|
811
|
+
}
|
|
812
|
+
rescue => e
|
|
813
|
+
{ status: "error", error: e.message }
|
|
814
|
+
end
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
Example: Strategy Backtest Tool
|
|
819
|
+
module Trading
|
|
820
|
+
module Tools
|
|
821
|
+
class BacktestStrategy < OllamaAgent::Tool
|
|
822
|
+
def name
|
|
823
|
+
"backtest_strategy"
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
def description
|
|
827
|
+
"Backtest a strategy on historical data"
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
def schema
|
|
831
|
+
{
|
|
832
|
+
strategy: "json",
|
|
833
|
+
symbol: "string"
|
|
834
|
+
}
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
def call(args)
|
|
838
|
+
result = StrategyBacktester.run(
|
|
839
|
+
strategy: args["strategy"],
|
|
840
|
+
symbol: args["symbol"]
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
{
|
|
844
|
+
status: "success",
|
|
845
|
+
pnl: result.pnl,
|
|
846
|
+
win_rate: result.win_rate,
|
|
847
|
+
drawdown: result.drawdown
|
|
848
|
+
}
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
Critical invariant (do not break this)
|
|
854
|
+
LLM NEVER touches trading execution directly
|
|
855
|
+
# ❌ forbidden
|
|
856
|
+
llm.place_order
|
|
857
|
+
|
|
858
|
+
# ✅ enforced
|
|
859
|
+
tool -> executor -> risk_manager -> order_executor
|
|
860
|
+
Risk enforcement layer (must exist in trading system)
|
|
861
|
+
module Trading
|
|
862
|
+
class RiskManager
|
|
863
|
+
MAX_RISK_PER_TRADE = 0.01
|
|
864
|
+
|
|
865
|
+
def validate(tool_name, args)
|
|
866
|
+
case tool_name
|
|
867
|
+
when "place_order"
|
|
868
|
+
validate_order(args)
|
|
869
|
+
else
|
|
870
|
+
args
|
|
871
|
+
end
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
private
|
|
875
|
+
|
|
876
|
+
def validate_order(args)
|
|
877
|
+
raise "Missing SL" unless args["stop_loss"]
|
|
878
|
+
raise "Missing quantity" unless args["quantity"]
|
|
879
|
+
|
|
880
|
+
args
|
|
881
|
+
end
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
Tool Registration (integration point)
|
|
885
|
+
trading_tools = [
|
|
886
|
+
Trading::Tools::FetchOptionChain.new,
|
|
887
|
+
Trading::Tools::BacktestStrategy.new,
|
|
888
|
+
Trading::Tools::PlaceOrder.new,
|
|
889
|
+
Trading::Tools::ExitPosition.new
|
|
890
|
+
]
|
|
891
|
+
What this enables (important)
|
|
892
|
+
|
|
893
|
+
Once separated properly, your system becomes:
|
|
894
|
+
|
|
895
|
+
1. Swappable brains
|
|
896
|
+
model: "llama3" # local
|
|
897
|
+
model: "gpt-5" # cloud
|
|
898
|
+
model: "mixtral" # alt
|
|
899
|
+
2. Multi-agent orchestration (future)
|
|
900
|
+
Strategy generator agent
|
|
901
|
+
Backtesting agent
|
|
902
|
+
Execution agent
|
|
903
|
+
|
|
904
|
+
All using same runtime.
|
|
905
|
+
|
|
906
|
+
3. Deterministic trading
|
|
907
|
+
Agent suggests
|
|
908
|
+
System enforces
|
|
909
|
+
Critical mistakes to avoid
|
|
910
|
+
1. Leaking domain logic into gem ❌
|
|
911
|
+
|
|
912
|
+
If you add:
|
|
913
|
+
|
|
914
|
+
def calculate_rsi
|
|
915
|
+
|
|
916
|
+
inside ollama_agent → architecture is broken.
|
|
917
|
+
|
|
918
|
+
2. Fat tools ❌
|
|
919
|
+
|
|
920
|
+
Bad:
|
|
921
|
+
|
|
922
|
+
class TradeTool
|
|
923
|
+
def call
|
|
924
|
+
fetch + analyze + trade
|
|
925
|
+
end
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
Good:
|
|
929
|
+
|
|
930
|
+
fetch tool
|
|
931
|
+
indicator tool
|
|
932
|
+
decision tool
|
|
933
|
+
execution tool
|
|
934
|
+
3. No schema enforcement ❌
|
|
935
|
+
|
|
936
|
+
Every tool must define:
|
|
937
|
+
|
|
938
|
+
def schema
|
|
939
|
+
|
|
940
|
+
Otherwise LLM arguments drift → system breaks.
|
|
941
|
+
|
|
942
|
+
Final structure (clean, scalable)
|
|
943
|
+
/ollama_agent (gem)
|
|
944
|
+
→ generic runtime
|
|
945
|
+
|
|
946
|
+
/algo_trading_agent
|
|
947
|
+
→ tools/
|
|
948
|
+
→ strategies/
|
|
949
|
+
→ risk/
|
|
950
|
+
→ backtester/
|
|
951
|
+
|
|
952
|
+
/algo_trading_api (Rails)
|
|
953
|
+
→ orchestration
|
|
954
|
+
→ persistence
|
|
955
|
+
→ websocket
|
|
956
|
+
Bottom line
|
|
957
|
+
|
|
958
|
+
You are building:
|
|
959
|
+
|
|
960
|
+
Agent OS (ollama_agent) + Trading Engine (algo_trading_agent)
|
|
961
|
+
|
|
962
|
+
Not mixing them is what makes this scalable and production-safe.
|
|
963
|
+
|
|
964
|
+
Next step
|
|
965
|
+
|
|
966
|
+
You are ready for:
|
|
967
|
+
|
|
968
|
+
→ Plugin SDK + strict schema + tool validation layer
|
|
969
|
+
|
|
970
|
+
If you want the next jump:
|
|
971
|
+
|
|
972
|
+
Say:
|
|
973
|
+
|
|
974
|
+
“build tool sdk + schema validator + retry system”
|
|
975
|
+
|
|
976
|
+
That’s the next bottleneck before live deployment.
|
|
977
|
+
|
|
978
|
+
What is needed only for ollama_agent
|
|
979
|
+
|
|
980
|
+
Direct answer:
|
|
981
|
+
Your ollama_agent gem should contain only the minimal agent runtime primitives required to run a deterministic think → act → observe loop with tool execution.
|
|
982
|
+
|
|
983
|
+
Nothing domain-specific. Nothing optional.
|
|
984
|
+
|
|
985
|
+
✅ REQUIRED (and sufficient) for ollama_agent
|
|
986
|
+
1. Agent Runner (execution loop)
|
|
987
|
+
|
|
988
|
+
This is the only orchestrator.
|
|
989
|
+
|
|
990
|
+
module OllamaAgent
|
|
991
|
+
class Runner
|
|
992
|
+
MAX_STEPS = 10
|
|
993
|
+
|
|
994
|
+
def initialize(planner:, tool_registry:, executor:, memory:, logger:)
|
|
995
|
+
@planner = planner
|
|
996
|
+
@tool_registry = tool_registry
|
|
997
|
+
@executor = executor
|
|
998
|
+
@memory = memory
|
|
999
|
+
@logger = logger
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
def run(context:)
|
|
1003
|
+
steps = 0
|
|
1004
|
+
|
|
1005
|
+
loop do
|
|
1006
|
+
raise "Max steps exceeded" if steps >= MAX_STEPS
|
|
1007
|
+
|
|
1008
|
+
thought = @planner.next_step(context: context, memory: @memory)
|
|
1009
|
+
|
|
1010
|
+
action = @tool_registry.resolve(thought)
|
|
1011
|
+
raise "Invalid tool call: #{thought}" unless action
|
|
1012
|
+
|
|
1013
|
+
result = @executor.execute(action)
|
|
1014
|
+
|
|
1015
|
+
@memory.append(thought:, action:, result:)
|
|
1016
|
+
|
|
1017
|
+
break if result[:status] == "done"
|
|
1018
|
+
|
|
1019
|
+
steps += 1
|
|
1020
|
+
end
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
end
|
|
1024
|
+
2. Planner (LLM adapter)
|
|
1025
|
+
|
|
1026
|
+
Only responsibility:
|
|
1027
|
+
|
|
1028
|
+
Take context + memory
|
|
1029
|
+
Return strict JSON tool call
|
|
1030
|
+
module OllamaAgent
|
|
1031
|
+
class Planner
|
|
1032
|
+
def initialize(client:, model:)
|
|
1033
|
+
@client = client
|
|
1034
|
+
@model = model
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
def next_step(context:, memory:)
|
|
1038
|
+
prompt = build_prompt(context, memory)
|
|
1039
|
+
|
|
1040
|
+
response = @client.generate(model: @model, prompt: prompt)
|
|
1041
|
+
|
|
1042
|
+
extract_json(response)
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
private
|
|
1046
|
+
|
|
1047
|
+
def build_prompt(context, memory)
|
|
1048
|
+
<<~PROMPT
|
|
1049
|
+
You are an agent. Respond ONLY in JSON.
|
|
1050
|
+
|
|
1051
|
+
Context:
|
|
1052
|
+
#{context}
|
|
1053
|
+
|
|
1054
|
+
Memory:
|
|
1055
|
+
#{memory.recent}
|
|
1056
|
+
|
|
1057
|
+
Output:
|
|
1058
|
+
{ "tool": "name", "args": {} }
|
|
1059
|
+
PROMPT
|
|
1060
|
+
end
|
|
1061
|
+
|
|
1062
|
+
def extract_json(response)
|
|
1063
|
+
json = response[/\{.*\}/m]
|
|
1064
|
+
raise "No JSON returned" unless json
|
|
1065
|
+
|
|
1066
|
+
JSON.parse(json)
|
|
1067
|
+
end
|
|
1068
|
+
end
|
|
1069
|
+
end
|
|
1070
|
+
3. Tool Interface (plugin contract)
|
|
1071
|
+
|
|
1072
|
+
This defines your entire ecosystem.
|
|
1073
|
+
|
|
1074
|
+
module OllamaAgent
|
|
1075
|
+
class Tool
|
|
1076
|
+
def name
|
|
1077
|
+
raise NotImplementedError
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
def description
|
|
1081
|
+
raise NotImplementedError
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
def schema
|
|
1085
|
+
raise NotImplementedError
|
|
1086
|
+
end
|
|
1087
|
+
|
|
1088
|
+
def call(args)
|
|
1089
|
+
raise NotImplementedError
|
|
1090
|
+
end
|
|
1091
|
+
end
|
|
1092
|
+
end
|
|
1093
|
+
4. Tool Registry (resolution layer)
|
|
1094
|
+
module OllamaAgent
|
|
1095
|
+
class ToolRegistry
|
|
1096
|
+
def initialize(tools)
|
|
1097
|
+
@tools = tools.index_by(&:name)
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
def resolve(thought)
|
|
1101
|
+
return nil unless thought.is_a?(Hash)
|
|
1102
|
+
|
|
1103
|
+
tool = @tools[thought["tool"]]
|
|
1104
|
+
return nil unless tool
|
|
1105
|
+
|
|
1106
|
+
{
|
|
1107
|
+
tool: tool,
|
|
1108
|
+
args: thought["args"] || {}
|
|
1109
|
+
}
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
def descriptions
|
|
1113
|
+
@tools.values.map do |t|
|
|
1114
|
+
"#{t.name}: #{t.description}, schema: #{t.schema}"
|
|
1115
|
+
end.join("\n")
|
|
1116
|
+
end
|
|
1117
|
+
end
|
|
1118
|
+
end
|
|
1119
|
+
5. Executor (safety boundary)
|
|
1120
|
+
|
|
1121
|
+
Handles:
|
|
1122
|
+
|
|
1123
|
+
validation hook
|
|
1124
|
+
execution
|
|
1125
|
+
module OllamaAgent
|
|
1126
|
+
class Executor
|
|
1127
|
+
def initialize(validator: nil)
|
|
1128
|
+
@validator = validator
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
def execute(action)
|
|
1132
|
+
tool = action[:tool]
|
|
1133
|
+
args = action[:args]
|
|
1134
|
+
|
|
1135
|
+
args = @validator.validate(tool.name, args) if @validator
|
|
1136
|
+
|
|
1137
|
+
tool.call(args)
|
|
1138
|
+
rescue => e
|
|
1139
|
+
{ status: "error", error: e.message }
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
end
|
|
1143
|
+
6. Memory (short-term only)
|
|
1144
|
+
|
|
1145
|
+
No persistence. No intelligence.
|
|
1146
|
+
|
|
1147
|
+
module OllamaAgent
|
|
1148
|
+
class Memory
|
|
1149
|
+
def initialize(limit: 10)
|
|
1150
|
+
@steps = []
|
|
1151
|
+
@limit = limit
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
def append(thought:, action:, result:)
|
|
1155
|
+
@steps << { thought:, action:, result: }
|
|
1156
|
+
@steps.shift if @steps.size > @limit
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
def recent
|
|
1160
|
+
@steps
|
|
1161
|
+
end
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
7. JSON Enforcement (critical utility)
|
|
1165
|
+
|
|
1166
|
+
This is not optional — without it system breaks.
|
|
1167
|
+
|
|
1168
|
+
module OllamaAgent
|
|
1169
|
+
module JsonExtractor
|
|
1170
|
+
def self.extract(response)
|
|
1171
|
+
json = response[/\{.*\}/m]
|
|
1172
|
+
raise "Invalid JSON response" unless json
|
|
1173
|
+
|
|
1174
|
+
JSON.parse(json)
|
|
1175
|
+
end
|
|
1176
|
+
end
|
|
1177
|
+
end
|
|
1178
|
+
❌ EXCLUDE (strictly forbidden in gem)
|
|
1179
|
+
|
|
1180
|
+
Do NOT include:
|
|
1181
|
+
|
|
1182
|
+
Domain logic
|
|
1183
|
+
trading
|
|
1184
|
+
finance
|
|
1185
|
+
indicators
|
|
1186
|
+
strategies
|
|
1187
|
+
Tool implementations
|
|
1188
|
+
no DhanHQ
|
|
1189
|
+
no HTTP clients
|
|
1190
|
+
no APIs
|
|
1191
|
+
Persistence
|
|
1192
|
+
no ActiveRecord
|
|
1193
|
+
no DB
|
|
1194
|
+
no Redis
|
|
1195
|
+
Backtesting / evaluation
|
|
1196
|
+
belongs to domain system
|
|
1197
|
+
Risk management logic
|
|
1198
|
+
belongs to executor validator (external)
|
|
1199
|
+
Minimal Gem Structure
|
|
1200
|
+
ollama_agent/
|
|
1201
|
+
lib/
|
|
1202
|
+
ollama_agent/
|
|
1203
|
+
runner.rb
|
|
1204
|
+
planner.rb
|
|
1205
|
+
tool.rb
|
|
1206
|
+
tool_registry.rb
|
|
1207
|
+
executor.rb
|
|
1208
|
+
memory.rb
|
|
1209
|
+
json_extractor.rb
|
|
1210
|
+
Critical invariants
|
|
1211
|
+
1. LLM output MUST be JSON
|
|
1212
|
+
|
|
1213
|
+
If not → reject immediately.
|
|
1214
|
+
|
|
1215
|
+
2. Tools are the only execution path
|
|
1216
|
+
|
|
1217
|
+
No direct actions from planner.
|
|
1218
|
+
|
|
1219
|
+
3. Runner is stateless across runs
|
|
1220
|
+
|
|
1221
|
+
Memory resets unless injected.
|
|
1222
|
+
|
|
1223
|
+
4. Executor is the only mutation boundary
|
|
1224
|
+
|
|
1225
|
+
All side effects go through it.
|
|
1226
|
+
|
|
1227
|
+
What this gives you
|
|
1228
|
+
|
|
1229
|
+
With just this:
|
|
1230
|
+
|
|
1231
|
+
You can plug any tool system (trading, dev, scraping)
|
|
1232
|
+
You can swap models (Ollama, GPT, etc.)
|
|
1233
|
+
You get deterministic execution loop
|
|
1234
|
+
What this does NOT give you
|
|
1235
|
+
Intelligence ❌
|
|
1236
|
+
Profitability ❌
|
|
1237
|
+
Strategy edge ❌
|
|
1238
|
+
|
|
1239
|
+
That comes from your trading agent layer, not this gem.
|
|
1240
|
+
|
|
1241
|
+
Final line
|
|
1242
|
+
|
|
1243
|
+
If your gem contains more than the 7 components above:
|
|
1244
|
+
|
|
1245
|
+
You are overbuilding and corrupting the abstraction.
|
|
1246
|
+
|
|
1247
|
+
Next step
|
|
1248
|
+
|
|
1249
|
+
You now need:
|
|
1250
|
+
|
|
1251
|
+
→ Tool SDK (schema validation + retry + error normalization)
|
|
1252
|
+
|
|
1253
|
+
Say:
|
|
1254
|
+
|
|
1255
|
+
“build tool sdk layer”
|
|
1256
|
+
|
|
1257
|
+
That’s the next real bottleneck before your agent becomes reliable.
|
|
1258
|
+
|
|
1259
|
+
And what do we need for algo_trading_agent ?
|
|
1260
|
+
|
|
1261
|
+
Direct decision:
|
|
1262
|
+
Your algo_trading_agent is not an agent framework—it is a deterministic trading engine exposed as tools for ollama_agent.
|
|
1263
|
+
|
|
1264
|
+
Design it as a tool-driven domain system with strict execution guarantees.
|
|
1265
|
+
|
|
1266
|
+
✅ REQUIRED SYSTEMS (no optional components)
|
|
1267
|
+
1. Market Data Layer (authoritative source)
|
|
1268
|
+
Responsibilities
|
|
1269
|
+
Fetch historical + intraday candles
|
|
1270
|
+
Real-time LTP via WebSocket (primary)
|
|
1271
|
+
Option chain snapshots
|
|
1272
|
+
Instrument resolution (NSE only)
|
|
1273
|
+
Structure
|
|
1274
|
+
module Trading
|
|
1275
|
+
module MarketData
|
|
1276
|
+
class CandleRepository
|
|
1277
|
+
def fetch(symbol:, interval:, from:, to:)
|
|
1278
|
+
Dhanhq::Client.new.historical_data(...)
|
|
1279
|
+
end
|
|
1280
|
+
end
|
|
1281
|
+
|
|
1282
|
+
class LtpCache
|
|
1283
|
+
def get(security_id:, segment:)
|
|
1284
|
+
Rails.cache.read(key(security_id, segment))
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1287
|
+
def set(security_id:, segment:, ltp:)
|
|
1288
|
+
Rails.cache.write(key(security_id, segment), ltp)
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
private
|
|
1292
|
+
|
|
1293
|
+
def key(id, seg)
|
|
1294
|
+
"ltp:#{seg}:#{id}"
|
|
1295
|
+
end
|
|
1296
|
+
end
|
|
1297
|
+
|
|
1298
|
+
class OptionChain
|
|
1299
|
+
def fetch(symbol:, expiry:)
|
|
1300
|
+
Dhanhq::Client.new.option_chain(...)
|
|
1301
|
+
end
|
|
1302
|
+
end
|
|
1303
|
+
end
|
|
1304
|
+
end
|
|
1305
|
+
2. Indicator Engine (pure computation)
|
|
1306
|
+
Rules
|
|
1307
|
+
No API calls
|
|
1308
|
+
Deterministic
|
|
1309
|
+
Operates on CandleSeries
|
|
1310
|
+
module Trading
|
|
1311
|
+
module Indicators
|
|
1312
|
+
class RSI
|
|
1313
|
+
def self.calculate(candles:, period: 14)
|
|
1314
|
+
# pure implementation
|
|
1315
|
+
end
|
|
1316
|
+
end
|
|
1317
|
+
|
|
1318
|
+
class Supertrend
|
|
1319
|
+
end
|
|
1320
|
+
|
|
1321
|
+
class MACD
|
|
1322
|
+
end
|
|
1323
|
+
end
|
|
1324
|
+
end
|
|
1325
|
+
3. Strategy DSL + Evaluator
|
|
1326
|
+
|
|
1327
|
+
You need structured strategies—not text blobs.
|
|
1328
|
+
|
|
1329
|
+
Strategy format
|
|
1330
|
+
strategy = {
|
|
1331
|
+
entry: [
|
|
1332
|
+
{ indicator: "rsi", condition: "<", value: 30 },
|
|
1333
|
+
{ indicator: "price", condition: "near_ob" }
|
|
1334
|
+
],
|
|
1335
|
+
exit: {
|
|
1336
|
+
rr: 2.0,
|
|
1337
|
+
sl_type: "swing_low"
|
|
1338
|
+
},
|
|
1339
|
+
instrument: "NIFTY",
|
|
1340
|
+
type: "CE"
|
|
1341
|
+
}
|
|
1342
|
+
Evaluator
|
|
1343
|
+
module Trading
|
|
1344
|
+
class StrategyEvaluator
|
|
1345
|
+
def initialize(indicator_engine:)
|
|
1346
|
+
@indicator_engine = indicator_engine
|
|
1347
|
+
end
|
|
1348
|
+
|
|
1349
|
+
def evaluate(strategy:, candles:)
|
|
1350
|
+
# returns signal true/false + metadata
|
|
1351
|
+
end
|
|
1352
|
+
end
|
|
1353
|
+
end
|
|
1354
|
+
4. Backtesting Engine (MANDATORY)
|
|
1355
|
+
|
|
1356
|
+
No backtesting = no system.
|
|
1357
|
+
|
|
1358
|
+
Requirements
|
|
1359
|
+
Candle replay
|
|
1360
|
+
Entry/exit simulation
|
|
1361
|
+
SL/TP enforcement
|
|
1362
|
+
Metrics generation
|
|
1363
|
+
module Trading
|
|
1364
|
+
class Backtester
|
|
1365
|
+
def run(strategy:, symbol:)
|
|
1366
|
+
candles = MarketData::CandleRepository.new.fetch(...)
|
|
1367
|
+
|
|
1368
|
+
trades = []
|
|
1369
|
+
|
|
1370
|
+
candles.each do |candle|
|
|
1371
|
+
# simulate entry/exit
|
|
1372
|
+
end
|
|
1373
|
+
|
|
1374
|
+
compute_metrics(trades)
|
|
1375
|
+
end
|
|
1376
|
+
|
|
1377
|
+
def compute_metrics(trades)
|
|
1378
|
+
{
|
|
1379
|
+
pnl: ...,
|
|
1380
|
+
win_rate: ...,
|
|
1381
|
+
max_drawdown: ...,
|
|
1382
|
+
profit_factor: ...
|
|
1383
|
+
}
|
|
1384
|
+
end
|
|
1385
|
+
end
|
|
1386
|
+
end
|
|
1387
|
+
5. Strategy Scoring Engine
|
|
1388
|
+
|
|
1389
|
+
This decides what goes live.
|
|
1390
|
+
|
|
1391
|
+
module Trading
|
|
1392
|
+
class StrategyScorer
|
|
1393
|
+
def score(metrics)
|
|
1394
|
+
(
|
|
1395
|
+
metrics[:win_rate] * 0.4 +
|
|
1396
|
+
metrics[:profit_factor] * 0.4 -
|
|
1397
|
+
metrics[:max_drawdown] * 0.2
|
|
1398
|
+
)
|
|
1399
|
+
end
|
|
1400
|
+
end
|
|
1401
|
+
end
|
|
1402
|
+
6. Risk Management (hard constraints)
|
|
1403
|
+
|
|
1404
|
+
LLM must NEVER control this.
|
|
1405
|
+
|
|
1406
|
+
module Trading
|
|
1407
|
+
class RiskManager
|
|
1408
|
+
MAX_RISK_PER_TRADE = 0.01
|
|
1409
|
+
|
|
1410
|
+
def position_size(capital:, entry:, stop_loss:)
|
|
1411
|
+
risk = capital * MAX_RISK_PER_TRADE
|
|
1412
|
+
(risk / (entry - stop_loss)).floor
|
|
1413
|
+
end
|
|
1414
|
+
|
|
1415
|
+
def validate_order!(order)
|
|
1416
|
+
raise "No SL" unless order[:stop_loss]
|
|
1417
|
+
raise "Invalid RR" unless order[:target] > order[:entry]
|
|
1418
|
+
|
|
1419
|
+
true
|
|
1420
|
+
end
|
|
1421
|
+
end
|
|
1422
|
+
end
|
|
1423
|
+
7. Execution Engine (DhanHQ integration)
|
|
1424
|
+
Responsibilities
|
|
1425
|
+
Place orders
|
|
1426
|
+
Modify SL/TP
|
|
1427
|
+
Exit positions
|
|
1428
|
+
Sync positions
|
|
1429
|
+
module Trading
|
|
1430
|
+
module Execution
|
|
1431
|
+
class OrderExecutor
|
|
1432
|
+
def place(order)
|
|
1433
|
+
Dhanhq::Client.new.place_order(...)
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1436
|
+
def modify(order_id:, sl:, tp:)
|
|
1437
|
+
Dhanhq::Client.new.modify_order(...)
|
|
1438
|
+
end
|
|
1439
|
+
|
|
1440
|
+
def exit(position_id:)
|
|
1441
|
+
Dhanhq::Client.new.exit_position(...)
|
|
1442
|
+
end
|
|
1443
|
+
end
|
|
1444
|
+
end
|
|
1445
|
+
end
|
|
1446
|
+
8. Position Manager (state machine)
|
|
1447
|
+
|
|
1448
|
+
Tracks:
|
|
1449
|
+
|
|
1450
|
+
active positions
|
|
1451
|
+
SL/TP
|
|
1452
|
+
trailing logic
|
|
1453
|
+
module Trading
|
|
1454
|
+
class PositionManager
|
|
1455
|
+
def initialize(cache:)
|
|
1456
|
+
@cache = cache
|
|
1457
|
+
end
|
|
1458
|
+
|
|
1459
|
+
def update(position, ltp)
|
|
1460
|
+
# trailing SL, BE logic
|
|
1461
|
+
end
|
|
1462
|
+
end
|
|
1463
|
+
end
|
|
1464
|
+
9. Strategy Memory (learning layer)
|
|
1465
|
+
|
|
1466
|
+
This is your edge.
|
|
1467
|
+
|
|
1468
|
+
module Trading
|
|
1469
|
+
class StrategyMemory
|
|
1470
|
+
def store(strategy:, metrics:, context:)
|
|
1471
|
+
StrategyRecord.create!(
|
|
1472
|
+
strategy: strategy,
|
|
1473
|
+
score: StrategyScorer.new.score(metrics),
|
|
1474
|
+
context: context
|
|
1475
|
+
)
|
|
1476
|
+
end
|
|
1477
|
+
|
|
1478
|
+
def best(context:)
|
|
1479
|
+
StrategyRecord.where(context: context)
|
|
1480
|
+
.order(score: :desc)
|
|
1481
|
+
.limit(5)
|
|
1482
|
+
end
|
|
1483
|
+
end
|
|
1484
|
+
end
|
|
1485
|
+
10. Tool Layer (bridge to ollama_agent)
|
|
1486
|
+
|
|
1487
|
+
This is the ONLY interface exposed to agent.
|
|
1488
|
+
|
|
1489
|
+
Example tools
|
|
1490
|
+
module Trading
|
|
1491
|
+
module Tools
|
|
1492
|
+
class EvaluateStrategy < OllamaAgent::Tool
|
|
1493
|
+
def name = "evaluate_strategy"
|
|
1494
|
+
|
|
1495
|
+
def schema
|
|
1496
|
+
{ strategy: "json", symbol: "string" }
|
|
1497
|
+
end
|
|
1498
|
+
|
|
1499
|
+
def call(args)
|
|
1500
|
+
result = StrategyEvaluator.new.evaluate(...)
|
|
1501
|
+
|
|
1502
|
+
{ status: "success", signal: result }
|
|
1503
|
+
end
|
|
1504
|
+
end
|
|
1505
|
+
|
|
1506
|
+
class BacktestStrategy < OllamaAgent::Tool
|
|
1507
|
+
def name = "backtest_strategy"
|
|
1508
|
+
|
|
1509
|
+
def call(args)
|
|
1510
|
+
metrics = Backtester.new.run(...)
|
|
1511
|
+
|
|
1512
|
+
{ status: "success", metrics: metrics }
|
|
1513
|
+
end
|
|
1514
|
+
end
|
|
1515
|
+
|
|
1516
|
+
class PlaceOrder < OllamaAgent::Tool
|
|
1517
|
+
def name = "place_order"
|
|
1518
|
+
|
|
1519
|
+
def call(args)
|
|
1520
|
+
Trading::RiskManager.new.validate_order!(args)
|
|
1521
|
+
|
|
1522
|
+
Trading::Execution::OrderExecutor.new.place(args)
|
|
1523
|
+
|
|
1524
|
+
{ status: "done" }
|
|
1525
|
+
end
|
|
1526
|
+
end
|
|
1527
|
+
end
|
|
1528
|
+
end
|
|
1529
|
+
🔁 REQUIRED FLOW (system loop)
|
|
1530
|
+
|
|
1531
|
+
This is your actual system:
|
|
1532
|
+
|
|
1533
|
+
LLM →
|
|
1534
|
+
generate strategy →
|
|
1535
|
+
evaluate →
|
|
1536
|
+
backtest →
|
|
1537
|
+
score →
|
|
1538
|
+
(if valid) →
|
|
1539
|
+
place trade →
|
|
1540
|
+
manage position →
|
|
1541
|
+
store result →
|
|
1542
|
+
improve next iteration
|
|
1543
|
+
Critical invariants
|
|
1544
|
+
1. No direct execution from LLM
|
|
1545
|
+
|
|
1546
|
+
All trades must pass:
|
|
1547
|
+
|
|
1548
|
+
Tool → RiskManager → Executor
|
|
1549
|
+
2. Backtest before live (mandatory)
|
|
1550
|
+
|
|
1551
|
+
Never allow:
|
|
1552
|
+
|
|
1553
|
+
if signal
|
|
1554
|
+
place_order
|
|
1555
|
+
end
|
|
1556
|
+
|
|
1557
|
+
Instead:
|
|
1558
|
+
|
|
1559
|
+
metrics = backtest(...)
|
|
1560
|
+
return unless score > threshold
|
|
1561
|
+
3. Real-time LTP must come from WebSocket
|
|
1562
|
+
|
|
1563
|
+
Polling = latency = bad fills.
|
|
1564
|
+
|
|
1565
|
+
4. Strategy must be structured JSON
|
|
1566
|
+
|
|
1567
|
+
No natural language strategies.
|
|
1568
|
+
|
|
1569
|
+
Minimal folder structure
|
|
1570
|
+
algo_trading_agent/
|
|
1571
|
+
market_data/
|
|
1572
|
+
indicators/
|
|
1573
|
+
strategies/
|
|
1574
|
+
evaluator/
|
|
1575
|
+
backtester/
|
|
1576
|
+
scorer/
|
|
1577
|
+
risk/
|
|
1578
|
+
execution/
|
|
1579
|
+
positions/
|
|
1580
|
+
memory/
|
|
1581
|
+
tools/
|
|
1582
|
+
What this gives you
|
|
1583
|
+
Deterministic trading engine
|
|
1584
|
+
Backtest-driven execution
|
|
1585
|
+
Self-improving strategy loop
|
|
1586
|
+
Safe integration with agent runtime
|
|
1587
|
+
What will break you if missing
|
|
1588
|
+
Missing Result
|
|
1589
|
+
Backtester gambling
|
|
1590
|
+
RiskManager account blowup
|
|
1591
|
+
Tool layer no agent integration
|
|
1592
|
+
Memory no improvement
|
|
1593
|
+
WebSocket LTP bad execution
|
|
1594
|
+
Final line
|
|
1595
|
+
|
|
1596
|
+
ollama_agent = brain runtime
|
|
1597
|
+
algo_trading_agent = trading body
|
|
1598
|
+
|
|
1599
|
+
If you mix them → system becomes unscalable and unsafe.
|
|
1600
|
+
|
|
1601
|
+
Next step
|
|
1602
|
+
|
|
1603
|
+
You are ready for:
|
|
1604
|
+
|
|
1605
|
+
→ Full pipeline wiring (generate → backtest → rank → deploy → manage)
|
|
1606
|
+
|
|
1607
|
+
Say:
|
|
1608
|
+
|
|
1609
|
+
“build trading pipeline runner”
|
|
1610
|
+
|
|
1611
|
+
I’ll give you a complete orchestrator that plugs into your existing Rails + DhanHQ setup.
|
|
1612
|
+
|
|
1613
|
+
And what do we need in the /algo_trading_api rails app
|
|
1614
|
+
|
|
1615
|
+
Direct decision:
|
|
1616
|
+
Your /algo_trading_api Rails app is not where intelligence lives and not where the agent loop lives.
|
|
1617
|
+
|
|
1618
|
+
It is the orchestration + persistence + realtime execution layer.
|
|
1619
|
+
|
|
1620
|
+
Think:
|
|
1621
|
+
|
|
1622
|
+
ollama_agent = runtime
|
|
1623
|
+
algo_trading_agent = domain engine
|
|
1624
|
+
/algo_trading_api = production control plane
|
|
1625
|
+
|
|
1626
|
+
✅ REQUIRED SYSTEMS IN /algo_trading_api
|
|
1627
|
+
1. Orchestration Layer (entry point)
|
|
1628
|
+
|
|
1629
|
+
This is where everything is wired and triggered.
|
|
1630
|
+
|
|
1631
|
+
Responsibilities
|
|
1632
|
+
Trigger agent runs
|
|
1633
|
+
Inject tools
|
|
1634
|
+
Provide context (market state)
|
|
1635
|
+
Control execution mode (paper/live)
|
|
1636
|
+
Example
|
|
1637
|
+
# app/services/agent_runner_service.rb
|
|
1638
|
+
class AgentRunnerService
|
|
1639
|
+
def call
|
|
1640
|
+
agent = OllamaAgent::Runner.new(
|
|
1641
|
+
planner: planner,
|
|
1642
|
+
tool_registry: tool_registry,
|
|
1643
|
+
executor: executor,
|
|
1644
|
+
memory: memory,
|
|
1645
|
+
logger: Rails.logger
|
|
1646
|
+
)
|
|
1647
|
+
|
|
1648
|
+
agent.run(context: build_context)
|
|
1649
|
+
end
|
|
1650
|
+
|
|
1651
|
+
private
|
|
1652
|
+
|
|
1653
|
+
def planner
|
|
1654
|
+
OllamaAgent::Planner.new(
|
|
1655
|
+
client: OllamaClient.new,
|
|
1656
|
+
model: "llama3"
|
|
1657
|
+
)
|
|
1658
|
+
end
|
|
1659
|
+
|
|
1660
|
+
def tool_registry
|
|
1661
|
+
OllamaAgent::ToolRegistry.new(trading_tools)
|
|
1662
|
+
end
|
|
1663
|
+
|
|
1664
|
+
def trading_tools
|
|
1665
|
+
Trading::Tools::Registry.build
|
|
1666
|
+
end
|
|
1667
|
+
|
|
1668
|
+
def executor
|
|
1669
|
+
OllamaAgent::Executor.new(
|
|
1670
|
+
validator: Trading::RiskManager.new
|
|
1671
|
+
)
|
|
1672
|
+
end
|
|
1673
|
+
|
|
1674
|
+
def memory
|
|
1675
|
+
OllamaAgent::Memory.new
|
|
1676
|
+
end
|
|
1677
|
+
|
|
1678
|
+
def build_context
|
|
1679
|
+
{
|
|
1680
|
+
market: current_market_snapshot,
|
|
1681
|
+
capital: Portfolio.current.capital,
|
|
1682
|
+
positions: Position.active
|
|
1683
|
+
}.to_json
|
|
1684
|
+
end
|
|
1685
|
+
end
|
|
1686
|
+
2. Market State Aggregation (critical)
|
|
1687
|
+
|
|
1688
|
+
You already have WebSocket—this layer standardizes it.
|
|
1689
|
+
|
|
1690
|
+
Responsibilities
|
|
1691
|
+
Build a clean snapshot for agent
|
|
1692
|
+
Avoid raw feed noise
|
|
1693
|
+
# app/services/market_snapshot_builder.rb
|
|
1694
|
+
class MarketSnapshotBuilder
|
|
1695
|
+
def call
|
|
1696
|
+
{
|
|
1697
|
+
nifty_ltp: ltp("NIFTY"),
|
|
1698
|
+
banknifty_ltp: ltp("BANKNIFTY"),
|
|
1699
|
+
vix: vix,
|
|
1700
|
+
trend: trend,
|
|
1701
|
+
time: Time.current
|
|
1702
|
+
}
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
def ltp(symbol)
|
|
1706
|
+
Rails.cache.read("ltp:#{symbol}")
|
|
1707
|
+
end
|
|
1708
|
+
end
|
|
1709
|
+
3. Persistence Layer (non-negotiable)
|
|
1710
|
+
|
|
1711
|
+
You must track everything.
|
|
1712
|
+
|
|
1713
|
+
Tables
|
|
1714
|
+
strategies
|
|
1715
|
+
create_table :strategies do |t|
|
|
1716
|
+
t.jsonb :definition
|
|
1717
|
+
t.float :score
|
|
1718
|
+
t.string :context
|
|
1719
|
+
t.timestamps
|
|
1720
|
+
end
|
|
1721
|
+
backtests
|
|
1722
|
+
create_table :backtests do |t|
|
|
1723
|
+
t.references :strategy
|
|
1724
|
+
t.float :pnl
|
|
1725
|
+
t.float :win_rate
|
|
1726
|
+
t.float :drawdown
|
|
1727
|
+
t.timestamps
|
|
1728
|
+
end
|
|
1729
|
+
trades
|
|
1730
|
+
create_table :trades do |t|
|
|
1731
|
+
t.string :symbol
|
|
1732
|
+
t.float :entry
|
|
1733
|
+
t.float :exit
|
|
1734
|
+
t.float :pnl
|
|
1735
|
+
t.string :status
|
|
1736
|
+
t.timestamps
|
|
1737
|
+
end
|
|
1738
|
+
positions
|
|
1739
|
+
create_table :positions do |t|
|
|
1740
|
+
t.string :symbol
|
|
1741
|
+
t.float :entry
|
|
1742
|
+
t.float :stop_loss
|
|
1743
|
+
t.float :target
|
|
1744
|
+
t.string :status
|
|
1745
|
+
t.timestamps
|
|
1746
|
+
end
|
|
1747
|
+
4. Execution Mode Control (critical safety)
|
|
1748
|
+
|
|
1749
|
+
Never allow accidental live trading.
|
|
1750
|
+
|
|
1751
|
+
# config/application.yml
|
|
1752
|
+
TRADING_MODE=paper # or live
|
|
1753
|
+
class ExecutionMode
|
|
1754
|
+
def self.live?
|
|
1755
|
+
ENV["TRADING_MODE"] == "live"
|
|
1756
|
+
end
|
|
1757
|
+
end
|
|
1758
|
+
5. Order Execution Gateway (controlled entry)
|
|
1759
|
+
|
|
1760
|
+
Wrap your DhanHQ client here.
|
|
1761
|
+
|
|
1762
|
+
# app/services/order_gateway.rb
|
|
1763
|
+
class OrderGateway
|
|
1764
|
+
def place(order)
|
|
1765
|
+
if ExecutionMode.live?
|
|
1766
|
+
Dhanhq::Client.new.place_order(order)
|
|
1767
|
+
else
|
|
1768
|
+
PaperTradeSimulator.place(order)
|
|
1769
|
+
end
|
|
1770
|
+
end
|
|
1771
|
+
end
|
|
1772
|
+
6. Background Jobs (execution engine)
|
|
1773
|
+
|
|
1774
|
+
Everything runs async.
|
|
1775
|
+
|
|
1776
|
+
Use Sidekiq.
|
|
1777
|
+
|
|
1778
|
+
Jobs
|
|
1779
|
+
Agent run
|
|
1780
|
+
class AgentRunJob < ApplicationJob
|
|
1781
|
+
queue_as :default
|
|
1782
|
+
|
|
1783
|
+
def perform
|
|
1784
|
+
AgentRunnerService.new.call
|
|
1785
|
+
end
|
|
1786
|
+
end
|
|
1787
|
+
Position monitoring
|
|
1788
|
+
class PositionMonitorJob < ApplicationJob
|
|
1789
|
+
def perform
|
|
1790
|
+
Position.active.each do |pos|
|
|
1791
|
+
PositionManager.new.update(pos)
|
|
1792
|
+
end
|
|
1793
|
+
end
|
|
1794
|
+
end
|
|
1795
|
+
Strategy training loop
|
|
1796
|
+
class StrategyTrainingJob < ApplicationJob
|
|
1797
|
+
def perform
|
|
1798
|
+
StrategyTrainer.new.run
|
|
1799
|
+
end
|
|
1800
|
+
end
|
|
1801
|
+
7. Real-time Layer (WebSocket integration)
|
|
1802
|
+
|
|
1803
|
+
You already built this—ensure:
|
|
1804
|
+
|
|
1805
|
+
Requirements
|
|
1806
|
+
LTP cached per (securityId + segment)
|
|
1807
|
+
No polling fallback unless failure
|
|
1808
|
+
Broadcast updates internally
|
|
1809
|
+
8. API Layer (control + observability)
|
|
1810
|
+
|
|
1811
|
+
You need endpoints to control system.
|
|
1812
|
+
|
|
1813
|
+
Examples
|
|
1814
|
+
# POST /agent/run
|
|
1815
|
+
def run
|
|
1816
|
+
AgentRunJob.perform_later
|
|
1817
|
+
end
|
|
1818
|
+
|
|
1819
|
+
# GET /positions
|
|
1820
|
+
def index
|
|
1821
|
+
render json: Position.active
|
|
1822
|
+
end
|
|
1823
|
+
|
|
1824
|
+
# GET /strategies/top
|
|
1825
|
+
def top
|
|
1826
|
+
render json: Strategy.order(score: :desc).limit(10)
|
|
1827
|
+
end
|
|
1828
|
+
9. Logging + Audit (production critical)
|
|
1829
|
+
|
|
1830
|
+
Every step must be traceable.
|
|
1831
|
+
|
|
1832
|
+
Example
|
|
1833
|
+
Rails.logger.info({
|
|
1834
|
+
event: "agent_step",
|
|
1835
|
+
thought: thought,
|
|
1836
|
+
tool: action[:tool],
|
|
1837
|
+
result: result
|
|
1838
|
+
})
|
|
1839
|
+
10. Configuration Layer
|
|
1840
|
+
|
|
1841
|
+
Control everything centrally.
|
|
1842
|
+
|
|
1843
|
+
# config/trading.yml
|
|
1844
|
+
risk:
|
|
1845
|
+
max_per_trade: 0.01
|
|
1846
|
+
|
|
1847
|
+
strategy:
|
|
1848
|
+
min_score: 0.65
|
|
1849
|
+
|
|
1850
|
+
execution:
|
|
1851
|
+
max_positions: 3
|
|
1852
|
+
🔁 SYSTEM FLOW (actual production loop)
|
|
1853
|
+
Cron / Scheduler
|
|
1854
|
+
↓
|
|
1855
|
+
AgentRunJob
|
|
1856
|
+
↓
|
|
1857
|
+
OllamaAgent Runner
|
|
1858
|
+
↓
|
|
1859
|
+
Trading Tools (algo_trading_agent)
|
|
1860
|
+
↓
|
|
1861
|
+
Backtest + Score
|
|
1862
|
+
↓
|
|
1863
|
+
If valid → OrderGateway
|
|
1864
|
+
↓
|
|
1865
|
+
PositionManager
|
|
1866
|
+
↓
|
|
1867
|
+
WebSocket updates
|
|
1868
|
+
↓
|
|
1869
|
+
Persist results
|
|
1870
|
+
Critical invariants
|
|
1871
|
+
1. Rails app does NOT contain strategy logic
|
|
1872
|
+
|
|
1873
|
+
No:
|
|
1874
|
+
|
|
1875
|
+
if rsi < 30
|
|
1876
|
+
|
|
1877
|
+
That belongs in algo_trading_agent.
|
|
1878
|
+
|
|
1879
|
+
2. Rails app does NOT implement agent loop
|
|
1880
|
+
|
|
1881
|
+
No duplication of Runner.
|
|
1882
|
+
|
|
1883
|
+
3. All trades go through OrderGateway
|
|
1884
|
+
|
|
1885
|
+
No direct DhanHQ calls anywhere else.
|
|
1886
|
+
|
|
1887
|
+
4. Everything async
|
|
1888
|
+
|
|
1889
|
+
No blocking execution in controllers.
|
|
1890
|
+
|
|
1891
|
+
5. Paper mode default
|
|
1892
|
+
|
|
1893
|
+
Live mode must be explicitly enabled.
|
|
1894
|
+
|
|
1895
|
+
Minimal folder structure
|
|
1896
|
+
app/
|
|
1897
|
+
services/
|
|
1898
|
+
agent_runner_service.rb
|
|
1899
|
+
market_snapshot_builder.rb
|
|
1900
|
+
order_gateway.rb
|
|
1901
|
+
|
|
1902
|
+
jobs/
|
|
1903
|
+
agent_run_job.rb
|
|
1904
|
+
position_monitor_job.rb
|
|
1905
|
+
strategy_training_job.rb
|
|
1906
|
+
|
|
1907
|
+
models/
|
|
1908
|
+
strategy.rb
|
|
1909
|
+
backtest.rb
|
|
1910
|
+
trade.rb
|
|
1911
|
+
position.rb
|
|
1912
|
+
|
|
1913
|
+
controllers/
|
|
1914
|
+
agent_controller.rb
|
|
1915
|
+
positions_controller.rb
|
|
1916
|
+
strategies_controller.rb
|
|
1917
|
+
What this layer gives you
|
|
1918
|
+
Production control
|
|
1919
|
+
Safety (paper vs live)
|
|
1920
|
+
Observability
|
|
1921
|
+
Persistence
|
|
1922
|
+
Async execution
|
|
1923
|
+
What will break if missing
|
|
1924
|
+
Missing Result
|
|
1925
|
+
OrderGateway unsafe trades
|
|
1926
|
+
Jobs blocking system
|
|
1927
|
+
Persistence no learning
|
|
1928
|
+
Snapshot builder garbage inputs to agent
|
|
1929
|
+
Execution mode accidental real losses
|
|
1930
|
+
Final line
|
|
1931
|
+
|
|
1932
|
+
This Rails app is:
|
|
1933
|
+
|
|
1934
|
+
your trading system’s operating control plane
|
|
1935
|
+
|
|
1936
|
+
Not brain. Not logic. Not strategy.
|
|
1937
|
+
|
|
1938
|
+
Next step
|
|
1939
|
+
|
|
1940
|
+
You are ready for:
|
|
1941
|
+
|
|
1942
|
+
→ Full system wiring + scheduler + deployment plan
|
|
1943
|
+
|
|
1944
|
+
Say:
|
|
1945
|
+
|
|
1946
|
+
“build production scheduler + infra plan”
|
|
1947
|
+
|
|
1948
|
+
I’ll give you cron, queues, failover, and deployment architecture (K8s-ready).
|