@backtest-kit/ollama 13.6.0 โ 14.0.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.
- package/README.md +164 -237
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# ๐ค @backtest-kit/ollama
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> Universal LLM adapter for [backtest-kit](https://www.npmjs.com/package/backtest-kit) trading strategies. One higher-order-function API across **12 providers**, schema-enforced structured output, userspace prompt modules, token rotation โ plus an **LLM strategy optimizer** that generates runnable strategy code.
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -10,84 +10,92 @@
|
|
|
10
10
|
[](https://npmjs.org/package/@backtest-kit/ollama)
|
|
11
11
|
[]()
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
๐ **[Docs](https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html)** ยท ๐ **[Reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example)** ยท ๐ **[GitHub](https://github.com/tripolskypetr/backtest-kit)**
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npm install @backtest-kit/ollama backtest-kit agent-swarm-kit
|
|
17
|
+
```
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
---
|
|
18
20
|
|
|
19
|
-
##
|
|
21
|
+
## Why
|
|
20
22
|
|
|
21
|
-
-
|
|
22
|
-
- โก Higher-Order Functions: Wrap async functions with inference context via `di-scoped`
|
|
23
|
-
- ๐ Userspace Prompts: Load prompts from `.cjs` modules in `config/prompt/`
|
|
24
|
-
- ๐ฏ Userspace Schema: Define your own Zod or JSON schema with `addOutline`
|
|
25
|
-
- ๐ Token Rotation: Pass array of API keys for automatic rotation
|
|
26
|
-
- ๐๏ธ Memoized Cache: Prompt modules cached with `memoize` from `functools-kit`
|
|
23
|
+
AI strategies normally mean per-provider SDK boilerplate and JSON you can't trust. This package collapses all of it: wrap any async function with a provider HOF and it runs inside that provider's inference context โ swap `deepseek()` โ `claude()` โ `gpt5()` with no other change. Structured output is schema-enforced (Zod or JSON schema via `agent-swarm-kit`'s `addOutline`), prompts live as memoized userspace modules
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
- ๐ **12 providers** โ OpenAI, Claude, DeepSeek, Grok, Groq, Mistral, Perplexity, Cohere, Alibaba, Hugging Face, Ollama (local), GLM-4 (Z.ai).
|
|
26
|
+
- โก **Higher-order functions** โ wrap an async fn with inference context via `di-scoped`; same signature in, same out.
|
|
27
|
+
- ๐ฏ **Userspace schema** โ define your own Zod or JSON schema; structured output enforced with auto-retry + custom validations.
|
|
28
|
+
- ๐ **Userspace prompts** โ load from `.cjs` modules in `config/prompt/`, or inline; memoized via `functools-kit`.
|
|
29
|
+
- ๐ **Token rotation** โ pass an array of API keys for automatic rotation.
|
|
30
|
+
- ๐งฌ **Strategy optimizer** โ `Optimizer` generates *complete executable strategy code* from LLM analysis across training ranges.
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## The provider HOF
|
|
35
|
+
|
|
36
|
+
The whole adapter is one shape, repeated for 12 providers: `provider(fn, model, apiKey?) => fn`. It returns a function with the **same signature** as `fn`, executed inside the provider's inference context (so any `agent-swarm-kit` completion inside resolves to that provider).
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { deepseek } from '@backtest-kit/ollama';
|
|
40
|
+
import { addStrategy } from 'backtest-kit';
|
|
41
|
+
|
|
42
|
+
addStrategy({
|
|
43
|
+
strategyName: 'llm-signal', interval: '5m',
|
|
44
|
+
// swap deepseek() โ claude() / gpt5() / ollama() / groq() with no other change
|
|
45
|
+
getSignal: deepseek(getSignal, 'deepseek-chat', process.env.DEEPSEEK_API_KEY),
|
|
46
|
+
});
|
|
32
47
|
```
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
<details>
|
|
50
|
+
<summary>All 12 providers, base URLs & token rotation</summary>
|
|
51
|
+
|
|
52
|
+
| Provider | Function | Inference | Base URL |
|
|
53
|
+
|----------|----------|-----------|----------|
|
|
54
|
+
| OpenAI | `gpt5()` | `gpt5_inference` | `https://api.openai.com/v1/` |
|
|
55
|
+
| Claude | `claude()` | `claude_inference` | `https://api.anthropic.com/v1/` |
|
|
56
|
+
| DeepSeek | `deepseek()` | `deepseek_inference` | `https://api.deepseek.com/` |
|
|
57
|
+
| Grok (xAI) | `grok()` | `grok_inference` | `https://api.x.ai/v1/` |
|
|
58
|
+
| Groq | `groq()` | `groq_inference` | `https://api.groq.com/` |
|
|
59
|
+
| Mistral | `mistral()` | `mistral_inference` | `https://api.mistral.ai/v1/` |
|
|
60
|
+
| Perplexity | `perplexity()` | `perplexity_inference` | `https://api.perplexity.ai/` |
|
|
61
|
+
| Cohere | `cohere()` | `cohere_inference` | `https://api.cohere.ai/compatibility/v1/` |
|
|
62
|
+
| Alibaba (Qwen) | `alibaba()` | `alibaba_inference` | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1/` |
|
|
63
|
+
| Hugging Face | `hf()` | `hf_inference` | `https://router.huggingface.co/v1/` |
|
|
64
|
+
| Ollama (local) | `ollama()` | `ollama_inference` | `http://localhost:11434/` |
|
|
65
|
+
| GLM-4 (Z.ai) | `glm4()` | `glm4_inference` | `https://open.bigmodel.cn/api/paas/v4/` |
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// apiKey accepts a single key OR an array โ automatic rotation across calls
|
|
69
|
+
const wrappedFn = ollama(myFn, 'llama3.3:70b', ['key1', 'key2', 'key3']);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
All twelve share one signature โ `<T>(fn: T, model: string, apiKey?: string | string[]) => T` โ and run `fn` inside `ContextService.runInContext({ apiKey, inference, model })`. The matching `InferenceName` enum + per-provider `client/*Provider.client.ts` + `config/*.ts` resolve the actual SDK call.
|
|
73
|
+
|
|
74
|
+
</details>
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Structured output
|
|
35
79
|
|
|
36
|
-
|
|
80
|
+
Define a schema (Zod or raw JSON), register it as an outline against this package's `CompletionName`, and the LLM is forced to return valid JSON โ with custom validations that reject bad signals (e.g. "SL must be below entry for LONG").
|
|
81
|
+
|
|
82
|
+
<details>
|
|
83
|
+
<summary>Zod outline</summary>
|
|
37
84
|
|
|
38
85
|
```typescript
|
|
39
86
|
// schema/Signal.schema.ts
|
|
40
87
|
import { z } from 'zod';
|
|
41
|
-
import { str } from 'functools-kit';
|
|
42
|
-
|
|
43
88
|
export const SignalSchema = z.object({
|
|
44
|
-
position: z.enum(['long', 'short', 'wait']).describe(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
51
|
-
),
|
|
52
|
-
price_open: z.number().describe(
|
|
53
|
-
str.newline(
|
|
54
|
-
'Entry price in USD',
|
|
55
|
-
'Current market price or limit order price',
|
|
56
|
-
)
|
|
57
|
-
),
|
|
58
|
-
price_stop_loss: z.number().describe(
|
|
59
|
-
str.newline(
|
|
60
|
-
'Stop-loss price in USD',
|
|
61
|
-
'LONG: below price_open',
|
|
62
|
-
'SHORT: above price_open',
|
|
63
|
-
)
|
|
64
|
-
),
|
|
65
|
-
price_take_profit: z.number().describe(
|
|
66
|
-
str.newline(
|
|
67
|
-
'Take-profit price in USD',
|
|
68
|
-
'LONG: above price_open',
|
|
69
|
-
'SHORT: below price_open',
|
|
70
|
-
)
|
|
71
|
-
),
|
|
72
|
-
minute_estimated_time: z.number().describe(
|
|
73
|
-
'Estimated time to reach TP in minutes'
|
|
74
|
-
),
|
|
75
|
-
risk_note: z.string().describe(
|
|
76
|
-
str.newline(
|
|
77
|
-
'Risk assessment:',
|
|
78
|
-
'- Whale manipulations',
|
|
79
|
-
'- Order book imbalance',
|
|
80
|
-
'- Technical divergences',
|
|
81
|
-
'Provide specific numbers and percentages',
|
|
82
|
-
)
|
|
83
|
-
),
|
|
89
|
+
position: z.enum(['long', 'short', 'wait']).describe('long: bullish ยท short: bearish ยท wait: unclear'),
|
|
90
|
+
price_open: z.number().describe('Entry price in USD'),
|
|
91
|
+
price_stop_loss: z.number().describe('LONG: below entry ยท SHORT: above entry'),
|
|
92
|
+
price_take_profit: z.number().describe('LONG: above entry ยท SHORT: below entry'),
|
|
93
|
+
minute_estimated_time: z.number().describe('Estimated minutes to reach TP'),
|
|
94
|
+
risk_note: z.string().describe('Whale manipulation, order-book imbalance, divergences โ with numbers'),
|
|
84
95
|
});
|
|
85
|
-
|
|
86
96
|
export type TSignalSchema = z.infer<typeof SignalSchema>;
|
|
87
97
|
```
|
|
88
98
|
|
|
89
|
-
### Signal Outline with Zod (userspace)
|
|
90
|
-
|
|
91
99
|
```typescript
|
|
92
100
|
// outline/signal.outline.ts
|
|
93
101
|
import { addOutline } from 'agent-swarm-kit';
|
|
@@ -99,28 +107,22 @@ addOutline<TSignalSchema>({
|
|
|
99
107
|
outlineName: 'SignalOutline',
|
|
100
108
|
completion: CompletionName.RunnerOutlineCompletion,
|
|
101
109
|
format: zodResponseFormat(SignalSchema, 'position_decision'),
|
|
102
|
-
getOutlineHistory: async ({ history, param: messages = [] }) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
validate: ({ data }) => {
|
|
108
|
-
if (data.position === 'long' && data.price_stop_loss >= data.price_open) {
|
|
109
|
-
throw new Error('For LONG, stop_loss must be below price_open');
|
|
110
|
-
}
|
|
111
|
-
if (data.position === 'short' && data.price_stop_loss <= data.price_open) {
|
|
112
|
-
throw new Error('For SHORT, stop_loss must be above price_open');
|
|
113
|
-
}
|
|
114
|
-
},
|
|
110
|
+
getOutlineHistory: async ({ history, param: messages = [] }) => { await history.push(messages); },
|
|
111
|
+
validations: [{
|
|
112
|
+
validate: ({ data }) => {
|
|
113
|
+
if (data.position === 'long' && data.price_stop_loss >= data.price_open) throw new Error('LONG: SL must be below entry');
|
|
114
|
+
if (data.position === 'short' && data.price_stop_loss <= data.price_open) throw new Error('SHORT: SL must be above entry');
|
|
115
115
|
},
|
|
116
|
-
],
|
|
116
|
+
}],
|
|
117
117
|
});
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary>Raw JSON-schema outline (no Zod)</summary>
|
|
121
124
|
|
|
122
125
|
```typescript
|
|
123
|
-
// outline/signal.outline.ts
|
|
124
126
|
import { addOutline, IOutlineFormat } from 'agent-swarm-kit';
|
|
125
127
|
import { CompletionName } from '@backtest-kit/ollama';
|
|
126
128
|
|
|
@@ -128,51 +130,40 @@ const format: IOutlineFormat = {
|
|
|
128
130
|
type: 'object',
|
|
129
131
|
properties: {
|
|
130
132
|
take_profit_price: { type: 'number', description: 'Take profit price in USD' },
|
|
131
|
-
stop_loss_price:
|
|
132
|
-
description:
|
|
133
|
-
reasoning:
|
|
133
|
+
stop_loss_price: { type: 'number', description: 'Stop-loss price in USD' },
|
|
134
|
+
description: { type: 'string', description: 'User-friendly risk explanation, min 10 sentences' },
|
|
135
|
+
reasoning: { type: 'string', description: 'Technical analysis, min 15 sentences' },
|
|
134
136
|
},
|
|
135
137
|
required: ['take_profit_price', 'stop_loss_price', 'description', 'reasoning'],
|
|
136
138
|
};
|
|
137
139
|
|
|
138
140
|
addOutline({
|
|
139
|
-
outlineName: 'SignalOutline',
|
|
140
|
-
|
|
141
|
-
prompt: 'Generate crypto trading signals based on price and volume indicators in JSON format.',
|
|
142
|
-
completion: CompletionName.RunnerOutlineCompletion,
|
|
141
|
+
outlineName: 'SignalOutline', format, completion: CompletionName.RunnerOutlineCompletion,
|
|
142
|
+
prompt: 'Generate crypto trading signals from price & volume indicators in JSON.',
|
|
143
143
|
getOutlineHistory: async ({ history, param }) => {
|
|
144
|
-
const
|
|
145
|
-
await commitReports(history,
|
|
144
|
+
const report = await ioc.signalReportService.getSignalReport(param);
|
|
145
|
+
await commitReports(history, report);
|
|
146
146
|
await history.push({ role: 'user', content: 'Generate JSON based on reports.' });
|
|
147
147
|
},
|
|
148
148
|
validations: [
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
if (data.action !== 'buy') return;
|
|
152
|
-
const stopLossChange = percentDiff(data.current_price, data.stop_loss_price);
|
|
153
|
-
if (stopLossChange > CC_LADDER_STOP_LOSS) {
|
|
154
|
-
throw new Error(`Stop loss must not exceed -${CC_LADDER_STOP_LOSS}%`);
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
docDescription: 'Checks stop-loss price against max loss percentage.',
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
validate: ({ data }) => {
|
|
161
|
-
if (data.action !== 'buy') return;
|
|
162
|
-
const sellChange = percentDiff(data.current_price, data.take_profit_price);
|
|
163
|
-
if (sellChange > CC_LADDER_TAKE_PROFIT) {
|
|
164
|
-
throw new Error(`Take profit must not exceed +${CC_LADDER_TAKE_PROFIT}%`);
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
docDescription: 'Checks take-profit price against max profit percentage.',
|
|
168
|
-
},
|
|
149
|
+
{ docDescription: 'Stop-loss vs max loss %', validate: ({ data }) => { if (data.action === 'buy' && percentDiff(data.current_price, data.stop_loss_price) > CC_LADDER_STOP_LOSS) throw new Error(`SL must not exceed -${CC_LADDER_STOP_LOSS}%`); } },
|
|
150
|
+
{ docDescription: 'Take-profit vs max profit %', validate: ({ data }) => { if (data.action === 'buy' && percentDiff(data.current_price, data.take_profit_price) > CC_LADDER_TAKE_PROFIT) throw new Error(`TP must not exceed +${CC_LADDER_TAKE_PROFIT}%`); } },
|
|
169
151
|
],
|
|
170
152
|
});
|
|
171
153
|
```
|
|
172
154
|
|
|
173
|
-
|
|
155
|
+
</details>
|
|
174
156
|
|
|
175
|
-
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Prompts
|
|
160
|
+
|
|
161
|
+
Prompt modules receive trading context automatically. `system` may be a string array or a function of `(symbol, strategyName, exchangeName, frameName, backtest)`; `user` likewise.
|
|
162
|
+
|
|
163
|
+
<details>
|
|
164
|
+
<summary>Module file, inline prompt & commitPrompt</summary>
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
176
167
|
// config/prompt/signal.prompt.cjs
|
|
177
168
|
module.exports = {
|
|
178
169
|
system: (symbol, strategyName, exchangeName, frameName, backtest) => [
|
|
@@ -180,180 +171,116 @@ module.exports = {
|
|
|
180
171
|
`Strategy: ${strategyName}, Timeframe: ${frameName}`,
|
|
181
172
|
backtest ? 'Backtest mode' : 'Live mode',
|
|
182
173
|
],
|
|
183
|
-
user: (symbol) => `Analyze ${symbol} and return trading decision`,
|
|
174
|
+
user: (symbol) => `Analyze ${symbol} and return a trading decision`,
|
|
184
175
|
};
|
|
185
176
|
```
|
|
186
177
|
|
|
187
|
-
### Strategy
|
|
188
|
-
|
|
189
178
|
```typescript
|
|
190
|
-
|
|
191
|
-
|
|
179
|
+
import { Module, Prompt, commitPrompt, MessageModel } from '@backtest-kit/ollama';
|
|
180
|
+
|
|
181
|
+
// from a .cjs module (default baseDir: {cwd}/config/prompt/), memoized
|
|
182
|
+
const signalModule = Module.fromPath('./signal.prompt.cjs');
|
|
183
|
+
// or inline
|
|
184
|
+
const inline = Prompt.fromPrompt({ system: ['You are a trading bot'], user: (symbol) => `Trend for ${symbol}?` });
|
|
192
185
|
|
|
186
|
+
const messages: MessageModel[] = [];
|
|
187
|
+
await commitPrompt(signalModule, messages); // pushes rendered system + user messages with context
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Full strategy: register the outline, build messages from a prompt, request structured JSON, wrap with a provider HOF:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import './outline/signal.outline';
|
|
193
194
|
import { deepseek, Module, commitPrompt, MessageModel } from '@backtest-kit/ollama';
|
|
194
195
|
import { addStrategy } from 'backtest-kit';
|
|
195
196
|
import { json } from 'agent-swarm-kit';
|
|
196
197
|
|
|
197
198
|
const signalModule = Module.fromPath('./signal.prompt.cjs');
|
|
198
|
-
|
|
199
199
|
const getSignal = async () => {
|
|
200
200
|
const messages: MessageModel[] = [];
|
|
201
201
|
await commitPrompt(signalModule, messages);
|
|
202
|
-
|
|
203
202
|
const { data } = await json('SignalOutline', messages);
|
|
204
203
|
return data;
|
|
205
204
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
strategyName: 'llm-signal',
|
|
209
|
-
interval: '5m',
|
|
210
|
-
getSignal: deepseek(getSignal, 'deepseek-chat', process.env.DEEPSEEK_API_KEY),
|
|
211
|
-
});
|
|
205
|
+
addStrategy({ strategyName: 'llm-signal', interval: '5m',
|
|
206
|
+
getSignal: deepseek(getSignal, 'deepseek-chat', process.env.DEEPSEEK_API_KEY) });
|
|
212
207
|
```
|
|
213
208
|
|
|
214
|
-
|
|
209
|
+
</details>
|
|
215
210
|
|
|
216
|
-
|
|
217
|
-
// config/prompt/risk.prompt.cjs
|
|
218
|
-
module.exports = {
|
|
219
|
-
system: ['You are a risk analyst', 'Be conservative'],
|
|
220
|
-
user: (symbol, strategyName, exchangeName, frameName, backtest) =>
|
|
221
|
-
`Evaluate risk for ${symbol} position on ${frameName} timeframe`,
|
|
222
|
-
};
|
|
223
|
-
```
|
|
211
|
+
---
|
|
224
212
|
|
|
225
|
-
|
|
213
|
+
## Debugging โ dump the conversation
|
|
226
214
|
|
|
227
|
-
|
|
228
|
-
import { Prompt, commitPrompt, MessageModel } from '@backtest-kit/ollama';
|
|
215
|
+
`dumpSignalData(signalId, history, signal, outputDir?)` archives the full LLM conversation attached to a signal, so an opaque model decision becomes a readable record. Skips if the directory already exists (never overwrites prior runs).
|
|
229
216
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
user: (symbol) => `What is the trend for ${symbol}?`,
|
|
233
|
-
});
|
|
217
|
+
<details>
|
|
218
|
+
<summary>What it writes</summary>
|
|
234
219
|
|
|
235
|
-
|
|
236
|
-
await commitPrompt(prompt, messages);
|
|
237
|
-
```
|
|
220
|
+
Into `{outputDir}/{signalId}/` (default `./dump/strategy`): `00_system_prompt.md` (system messages + output summary), numbered `XX_user_message.md` / `XX_assistant_message.md` per turn, and a final `XX_llm_output.md` with the signal DTO. Call it from `getSignal` right before returning the signal.
|
|
238
221
|
|
|
239
|
-
|
|
222
|
+
</details>
|
|
240
223
|
|
|
241
|
-
|
|
242
|
-
import { ollama } from '@backtest-kit/ollama';
|
|
224
|
+
---
|
|
243
225
|
|
|
244
|
-
|
|
245
|
-
```
|
|
226
|
+
## Strategy optimizer โ generate runnable strategy code
|
|
246
227
|
|
|
247
|
-
|
|
228
|
+
The most powerful piece, and the one the rest of the package feeds: `Optimizer` uses an LLM to analyze a symbol across training ranges and emit a **complete, executable strategy file** โ imports, helpers, strategies, walker, and launcher โ that you can run with backtest-kit directly.
|
|
248
229
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
| OpenAI | `gpt5()` | `https://api.openai.com/v1/` |
|
|
252
|
-
| Claude | `claude()` | `https://api.anthropic.com/v1/` |
|
|
253
|
-
| DeepSeek | `deepseek()` | `https://api.deepseek.com/` |
|
|
254
|
-
| Grok | `grok()` | `https://api.x.ai/v1/` |
|
|
255
|
-
| Mistral | `mistral()` | `https://api.mistral.ai/v1/` |
|
|
256
|
-
| Perplexity | `perplexity()` | `https://api.perplexity.ai/` |
|
|
257
|
-
| Cohere | `cohere()` | `https://api.cohere.ai/compatibility/v1/` |
|
|
258
|
-
| Alibaba | `alibaba()` | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1/` |
|
|
259
|
-
| Hugging Face | `hf()` | `https://router.huggingface.co/v1/` |
|
|
260
|
-
| Ollama | `ollama()` | `http://localhost:11434/` |
|
|
261
|
-
| Zhipu AI | `glm4()` | `https://open.bigmodel.cn/api/paas/v4/` |
|
|
262
|
-
|
|
263
|
-
## ๐ API
|
|
264
|
-
|
|
265
|
-
### Provider HOF
|
|
230
|
+
<details>
|
|
231
|
+
<summary>Optimizer API + addOptimizerSchema + progress events</summary>
|
|
266
232
|
|
|
267
233
|
```typescript
|
|
268
|
-
|
|
269
|
-
(fn, model, apiKey?) => fn
|
|
270
|
-
```
|
|
234
|
+
import { Optimizer, addOptimizerSchema, listenOptimizerProgress } from '@backtest-kit/ollama';
|
|
271
235
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
Module.fromPath(path: string, baseDir?: string): Module
|
|
276
|
-
```
|
|
236
|
+
// describe sources, training ranges, strategy/template generation (see IOptimizer* interfaces)
|
|
237
|
+
addOptimizerSchema({ optimizerName: 'my-optimizer', /* sources, ranges, strategy, template */ });
|
|
277
238
|
|
|
278
|
-
|
|
239
|
+
listenOptimizerProgress((p) => console.log(p)); // ProgressOptimizerContract
|
|
279
240
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
Prompt.fromPrompt(source: PromptModel): Prompt
|
|
241
|
+
const strategies = await Optimizer.getData('BTCUSDT', { optimizerName: 'my-optimizer' }); // metadata + LLM context per range
|
|
242
|
+
const code = await Optimizer.getCode('BTCUSDT', { optimizerName: 'my-optimizer' }); // full TS/JS source as string
|
|
243
|
+
await Optimizer.dump('BTCUSDT', { optimizerName: 'my-optimizer' }, './output'); // writes {optimizerName}_{symbol}.mjs
|
|
284
244
|
```
|
|
285
245
|
|
|
286
|
-
|
|
246
|
+
`getData` fetches from all sources and builds the LLM conversation per training range; `getCode` assembles the executable strategy; `dump` writes it to `{optimizerName}_{symbol}.mjs`. Companion registry functions: `getOptimizerSchema`, `listOptimizerSchema`, and `listenError`. The engine behind it is `common/ClientOptimizer.ts` driven by the `IOptimizer*` interfaces (`IOptimizerSchema`, `IOptimizerSource`, `IOptimizerStrategy`, `IOptimizerTemplate`, `IOptimizerRange`, `IOptimizerData`, `IOptimizerFetchArgs`, `IOptimizerFilterArgs`, `IOptimizerCallbacks`).
|
|
287
247
|
|
|
288
|
-
|
|
289
|
-
async function commitPrompt(source: Module | Prompt, history: MessageModel[]): Promise<void>
|
|
290
|
-
```
|
|
248
|
+
</details>
|
|
291
249
|
|
|
292
|
-
|
|
250
|
+
---
|
|
293
251
|
|
|
294
|
-
|
|
295
|
-
interface PromptModel {
|
|
296
|
-
system?: string[] | SystemPromptFn;
|
|
297
|
-
user: string | UserPromptFn;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
type SystemPromptFn = (
|
|
301
|
-
symbol: string,
|
|
302
|
-
strategyName: string,
|
|
303
|
-
exchangeName: string,
|
|
304
|
-
frameName: string,
|
|
305
|
-
backtest: boolean
|
|
306
|
-
) => Promise<string[]> | string[];
|
|
307
|
-
|
|
308
|
-
type UserPromptFn = (
|
|
309
|
-
symbol: string,
|
|
310
|
-
strategyName: string,
|
|
311
|
-
exchangeName: string,
|
|
312
|
-
frameName: string,
|
|
313
|
-
backtest: boolean
|
|
314
|
-
) => Promise<string> | string;
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
## ๐ก Why Use @backtest-kit/ollama?
|
|
318
|
-
|
|
319
|
-
Instead of manually integrating LLM SDKs:
|
|
320
|
-
|
|
321
|
-
**โ Without ollama (manual work)**
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
import OpenAI from 'openai';
|
|
325
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
326
|
-
|
|
327
|
-
const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY });
|
|
328
|
-
const response = await openai.chat.completions.create({
|
|
329
|
-
model: 'gpt-4o',
|
|
330
|
-
messages,
|
|
331
|
-
response_format: { type: 'json_object' }
|
|
332
|
-
});
|
|
333
|
-
const signal = JSON.parse(response.choices[0].message.content);
|
|
334
|
-
// ... manual schema validation
|
|
335
|
-
// ... manual error handling
|
|
336
|
-
// ... no fallback
|
|
337
|
-
```
|
|
252
|
+
## API reference
|
|
338
253
|
|
|
339
|
-
|
|
254
|
+
| Export | Description |
|
|
255
|
+
|--------|-------------|
|
|
256
|
+
| `ollama` `gpt5` `claude` `deepseek` `grok` `groq` `mistral` `perplexity` `cohere` `alibaba` `hf` `glm4` | Provider HOFs โ `(fn, model, apiKey?) => fn` |
|
|
257
|
+
| `CompletionName` | Completion-name enum for `agent-swarm-kit` outlines (`RunnerOutlineCompletion`, โฆ) |
|
|
258
|
+
| `Module.fromPath(path, baseDir?)` | Load a prompt `.cjs` module (default baseDir `{cwd}/config/prompt/`) |
|
|
259
|
+
| `Prompt.fromPrompt(source)` | Build a prompt from an inline `PromptModel` |
|
|
260
|
+
| `commitPrompt(source, history)` | Render a Module/Prompt's system+user messages into `history` |
|
|
261
|
+
| `dumpSignalData(id, history, signal, dir?)` | Archive the LLM conversation for one signal |
|
|
262
|
+
| `validate(...)` | Validate an outline result |
|
|
263
|
+
| `Optimizer` | `.getData` / `.getCode` / `.dump` โ LLM strategy-code generation |
|
|
264
|
+
| `addOptimizerSchema` ยท `getOptimizerSchema` ยท `listOptimizerSchema` | Optimizer schema registry |
|
|
265
|
+
| `listenOptimizerProgress` ยท `listenError` | Optimizer progress / error events |
|
|
266
|
+
| `MessageModel` `MessageRole` `PromptModel` | Message & prompt models |
|
|
267
|
+
| `IOptimizer*` ยท `ProgressOptimizerContract` | Optimizer interfaces & progress contract |
|
|
268
|
+
| `lib` | The internal engine (IoC container) for advanced use |
|
|
340
269
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
```
|
|
270
|
+
<details>
|
|
271
|
+
<summary>Complete source map</summary>
|
|
344
272
|
|
|
345
|
-
|
|
273
|
+
- `function/signal.function.ts` โ the 12 provider HOFs. `function/{add,get,list,event,setup,validate,history,dump,signal}.ts` โ registry, events, `setLogger`, `commitPrompt`, `dumpSignalData`.
|
|
274
|
+
- `client/*Provider.client.ts` (12) โ per-provider SDK adapters. `config/*.ts` โ per-provider base URLs/params, `ollama.rotate.ts` (token rotation), `params.ts`, `emitters.ts`.
|
|
275
|
+
- `classes/` โ `Module`, `Prompt`, `Optimizer`. `common/ClientOptimizer.ts` โ the optimizer engine.
|
|
276
|
+
- `enum/` โ `InferenceName` (12), `CompletionName`. `interface/Optimizer.interface.ts`, `contract/ProgressOptimizer.contract.ts`, `model/{Message,Prompt}.model.ts`.
|
|
277
|
+
- `helpers/{toLintMarkdown,toPlainString}.ts`, `lib/` (IoC: `core/{di,provide,types}`, services). Nothing in `src/` is undocumented.
|
|
346
278
|
|
|
347
|
-
|
|
348
|
-
- ๐ฏ Enforced JSON schema (no parsing errors)
|
|
349
|
-
- ๐ Built-in token rotation (Ollama)
|
|
350
|
-
- ๐ Context-based API keys
|
|
351
|
-
- ๐ก๏ธ Type-safe TypeScript interfaces
|
|
352
|
-
- ๐ Trading-specific output format
|
|
279
|
+
</details>
|
|
353
280
|
|
|
354
281
|
## ๐ค Contribute
|
|
355
282
|
|
|
356
|
-
Fork/PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
|
|
283
|
+
Fork / PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
|
|
357
284
|
|
|
358
285
|
## ๐ License
|
|
359
286
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/ollama",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"description": "Multi-provider LLM inference library for AI-powered trading strategies. Supports 10+ providers including OpenAI, Claude, DeepSeek, Grok, Mistral with unified API and automatic token rotation.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|
|
@@ -81,24 +81,24 @@
|
|
|
81
81
|
"ts-morph": "27.0.2",
|
|
82
82
|
"tslib": "2.7.0",
|
|
83
83
|
"typedoc": "0.27.9",
|
|
84
|
-
"backtest-kit": "
|
|
85
|
-
"worker-testbed": "2.
|
|
84
|
+
"backtest-kit": "14.0.0",
|
|
85
|
+
"worker-testbed": "2.1.0"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"@huggingface/inference": "^4.7.1",
|
|
89
89
|
"@langchain/core": "^0.3.57",
|
|
90
90
|
"@langchain/xai": "^0.0.2",
|
|
91
|
-
"backtest-kit": "^
|
|
91
|
+
"backtest-kit": "^14.0.0",
|
|
92
92
|
"groq-sdk": "^0.37.0",
|
|
93
93
|
"ollama": "^0.6.0",
|
|
94
94
|
"openai": "^4.97.0",
|
|
95
95
|
"typescript": "^5.0.0"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"agent-swarm-kit": "^2.
|
|
98
|
+
"agent-swarm-kit": "^3.2.0",
|
|
99
99
|
"di-kit": "^1.1.1",
|
|
100
100
|
"di-scoped": "^1.0.21",
|
|
101
|
-
"functools-kit": "^
|
|
101
|
+
"functools-kit": "^3.0.0",
|
|
102
102
|
"get-moment-stamp": "^2.0.0",
|
|
103
103
|
"jsonrepair": "^3.12.0",
|
|
104
104
|
"markdown-it": "^14.1.0",
|