@backtest-kit/ollama 0.2.0 → 1.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/build/index.mjs CHANGED
@@ -1,14 +1,14 @@
1
- import { json, event, validateToolArguments, RoundRobin, addCompletion, addOutline, validate as validate$1, overrideOutline } from 'agent-swarm-kit';
1
+ import { event, validateToolArguments, RoundRobin, addCompletion } from 'agent-swarm-kit';
2
2
  import { scoped } from 'di-scoped';
3
- import { createActivator } from 'di-kit';
4
- import { Markdown, getContext, getMode } from 'backtest-kit';
5
3
  import path, { join } from 'path';
4
+ import { createRequire } from 'module';
5
+ import { createActivator } from 'di-kit';
6
+ import { memoize, ToolRegistry, Subject, trycatch, errorData, getErrorMessage, iterateDocuments, distinctDocuments, resolveDocuments, singleshot, randomString, fetchApi, str, timeout } from 'functools-kit';
7
+ import { Markdown, getSymbol, getContext, getMode } from 'backtest-kit';
6
8
  import MarkdownIt from 'markdown-it';
7
9
  import sanitizeHtml from 'sanitize-html';
8
10
  import { lint } from 'markdownlint/promise';
9
11
  import { applyFixes } from 'markdownlint';
10
- import { ToolRegistry, memoize, singleshot, Subject, trycatch, errorData, getErrorMessage, iterateDocuments, distinctDocuments, resolveDocuments, randomString, fetchApi, str, timeout } from 'functools-kit';
11
- import { createRequire } from 'module';
12
12
  import fs, { mkdir, writeFile } from 'fs/promises';
13
13
  import OpenAI from 'openai';
14
14
  import { AIMessage, SystemMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
@@ -18,8 +18,6 @@ import { InferenceClient } from '@huggingface/inference';
18
18
  import { ChatOpenAI } from '@langchain/openai';
19
19
  import { get, set } from 'lodash-es';
20
20
  import { Ollama } from 'ollama';
21
- import { zodResponseFormat } from 'openai/helpers/zod';
22
- import { z } from 'zod';
23
21
 
24
22
  /**
25
23
  * Enumeration of completion strategy types.
@@ -43,7 +41,6 @@ var CompletionName;
43
41
  /** Outline completion mode (structured JSON with schema validation) */
44
42
  CompletionName["RunnerOutlineCompletion"] = "runner_outline_completion";
45
43
  })(CompletionName || (CompletionName = {}));
46
- var CompletionName$1 = CompletionName;
47
44
 
48
45
  /**
49
46
  * Scoped context service for isolated execution contexts.
@@ -218,14 +215,6 @@ class LoggerService {
218
215
  */
219
216
  const { provide, inject, init, override } = createActivator("ollama");
220
217
 
221
- /**
222
- * Common service type identifiers.
223
- * Services used across the entire application.
224
- */
225
- const commonServices$1 = {
226
- /** Logger service for application-wide logging */
227
- loggerService: Symbol("loggerService"),
228
- };
229
218
  /**
230
219
  * Base service type identifiers.
231
220
  * Core foundational services.
@@ -233,6 +222,7 @@ const commonServices$1 = {
233
222
  const baseServices$1 = {
234
223
  /** Context service for scoped execution contexts */
235
224
  contextService: Symbol('contextService'),
225
+ loggerService: Symbol("loggerService"),
236
226
  };
237
227
  /**
238
228
  * Private service type identifiers.
@@ -241,8 +231,6 @@ const baseServices$1 = {
241
231
  const privateServices$1 = {
242
232
  /** Runner private service for AI provider operations */
243
233
  runnerPrivateService: Symbol('runnerPrivateService'),
244
- /** Outline private service for structured completions */
245
- outlinePrivateService: Symbol('outlinePrivateService'),
246
234
  };
247
235
  /**
248
236
  * Public service type identifiers.
@@ -251,20 +239,29 @@ const privateServices$1 = {
251
239
  const publicServices$1 = {
252
240
  /** Runner public service for context-managed AI operations */
253
241
  runnerPublicService: Symbol('runnerPublicService'),
254
- /** Outline public service for simplified structured completions */
255
- outlinePublicService: Symbol('outlinePublicService'),
256
242
  };
257
243
  const promptServices$1 = {
258
- signalPromptService: Symbol('signalPromptService'),
244
+ resolvePromptService: Symbol('resolvePromptService'),
245
+ };
246
+ const cacheServices$1 = {
247
+ promptCacheService: Symbol('promptCacheService'),
259
248
  };
260
249
  const markdownServices$1 = {
261
250
  outlineMarkdownService: Symbol('outlineMarkdownService'),
262
251
  };
263
- const optimizerServices$1 = {
252
+ const templateServices$1 = {
264
253
  optimizerTemplateService: Symbol('optimizerTemplateService'),
254
+ };
255
+ const schemaServices$1 = {
265
256
  optimizerSchemaService: Symbol('optimizerSchemaService'),
257
+ };
258
+ const validationServices$1 = {
266
259
  optimizerValidationService: Symbol('optimizerValidationService'),
260
+ };
261
+ const connectionServices$1 = {
267
262
  optimizerConnectionService: Symbol('optimizerConnectionService'),
263
+ };
264
+ const globalServices$1 = {
268
265
  optimizerGlobalService: Symbol('optimizerGlobalService'),
269
266
  };
270
267
  /**
@@ -283,15 +280,66 @@ const optimizerServices$1 = {
283
280
  * ```
284
281
  */
285
282
  const TYPES = {
286
- ...commonServices$1,
287
283
  ...baseServices$1,
288
284
  ...promptServices$1,
285
+ ...cacheServices$1,
289
286
  ...markdownServices$1,
290
- ...optimizerServices$1,
287
+ ...templateServices$1,
288
+ ...schemaServices$1,
289
+ ...validationServices$1,
290
+ ...connectionServices$1,
291
+ ...globalServices$1,
291
292
  ...privateServices$1,
292
293
  ...publicServices$1,
293
294
  };
294
295
 
296
+ const PROMPT_TYPE_SYMBOL = Symbol("prompt-type");
297
+ class Prompt {
298
+ constructor(source) {
299
+ this.source = source;
300
+ this.__type__ = PROMPT_TYPE_SYMBOL;
301
+ }
302
+ }
303
+ Prompt.fromPrompt = (source) => {
304
+ if (!source || typeof source !== "object") {
305
+ throw new Error("Source must be a valid PromptModel object");
306
+ }
307
+ return new Prompt(source);
308
+ };
309
+ Prompt.isPrompt = (value) => {
310
+ return (value !== null &&
311
+ typeof value === "object" &&
312
+ value.__type__ === PROMPT_TYPE_SYMBOL);
313
+ };
314
+
315
+ const require = createRequire(import.meta.url);
316
+ const REQUIRE_MODULE_FN = memoize(([module]) => module, (module) => {
317
+ const modulePath = require.resolve(join(module.baseDir, module.path));
318
+ return require(modulePath);
319
+ });
320
+ class PromptCacheService {
321
+ constructor() {
322
+ this.loggerService = inject(TYPES.loggerService);
323
+ this.readModule = (module) => {
324
+ this.loggerService.log("promptCacheService readModule", {
325
+ path: module.path,
326
+ baseDir: module.baseDir,
327
+ });
328
+ const source = REQUIRE_MODULE_FN(module);
329
+ return Prompt.fromPrompt(source);
330
+ };
331
+ this.clear = (module) => {
332
+ this.loggerService.log("promptCacheService clear", {
333
+ module,
334
+ });
335
+ if (module) {
336
+ REQUIRE_MODULE_FN.clear(module);
337
+ }
338
+ REQUIRE_MODULE_FN.clear();
339
+ };
340
+ }
341
+ }
342
+
295
343
  /**
296
344
  * Warning threshold for message size in kilobytes.
297
345
  * Messages exceeding this size trigger console warnings.
@@ -448,203 +496,6 @@ class OutlineMarkdownService {
448
496
  }
449
497
  }
450
498
 
451
- /**
452
- * Enumeration of supported JSON schema outlines.
453
- *
454
- * Defines unique identifiers for structured output schemas used with
455
- * LLM providers. Outlines enforce JSON schema validation for critical
456
- * data structures like trading signals.
457
- *
458
- * @example
459
- * ```typescript
460
- * import { OutlineName } from '@backtest-kit/ollama';
461
- *
462
- * const outlineName = OutlineName.SignalOutline;
463
- * ```
464
- */
465
- var OutlineName;
466
- (function (OutlineName) {
467
- /** Trading signal JSON schema for position, TP/SL, and risk parameters */
468
- OutlineName["SignalOutline"] = "signal_outline";
469
- })(OutlineName || (OutlineName = {}));
470
- var OutlineName$1 = OutlineName;
471
-
472
- /**
473
- * Lints and auto-fixes markdown content using markdownlint rules.
474
- *
475
- * Validates markdown syntax and applies automatic fixes for common issues
476
- * like inconsistent list markers, trailing spaces, and heading styles.
477
- * Returns the original content if no errors found or fixes cannot be applied.
478
- *
479
- * @param content - Raw markdown content to lint
480
- * @returns Promise resolving to linted markdown content
481
- *
482
- * @example
483
- * ```typescript
484
- * const markdown = "# Title\n\n\n## Subtitle"; // Multiple blank lines
485
- * const linted = await toLintMarkdown(markdown);
486
- * // Returns: "# Title\n\n## Subtitle" (extra blank line removed)
487
- * ```
488
- */
489
- const toLintMarkdown = async (content) => {
490
- if (!content) {
491
- return "";
492
- }
493
- const { content: errors } = await lint({ strings: { content } });
494
- if (!errors.length) {
495
- return content;
496
- }
497
- const value = applyFixes(content, errors);
498
- return value ? value : content;
499
- };
500
-
501
- /**
502
- * Converts markdown content to plain text with Telegram-compatible HTML formatting.
503
- *
504
- * Processes markdown through three stages:
505
- * 1. Lints and fixes markdown using markdownlint
506
- * 2. Renders markdown to HTML using markdown-it
507
- * 3. Sanitizes HTML to Telegram-compatible subset
508
- *
509
- * Supported tags: b, i, a, code, pre, s, u, tg-spoiler, blockquote, br
510
- * Transforms: headings removed, lists to bullets, multiple newlines collapsed
511
- *
512
- * @param content - Raw markdown content
513
- * @returns Promise resolving to sanitized plain text with HTML formatting
514
- *
515
- * @example
516
- * ```typescript
517
- * const markdown = "# Title\n**Bold** and *italic*\n- Item 1\n- Item 2";
518
- * const plain = await toPlainString(markdown);
519
- * // Returns: "Bold and italic\n• Item 1\n• Item 2"
520
- * ```
521
- */
522
- const toPlainString = async (content) => {
523
- if (!content) {
524
- return "";
525
- }
526
- const markdown = await toLintMarkdown(content);
527
- const md = new MarkdownIt({
528
- html: false,
529
- breaks: true,
530
- linkify: true,
531
- typographer: true,
532
- });
533
- let telegramHtml = md.render(markdown);
534
- telegramHtml = sanitizeHtml(telegramHtml, {
535
- allowedTags: [
536
- "b",
537
- "i",
538
- "a",
539
- "code",
540
- "pre",
541
- "s",
542
- "u",
543
- "tg-spoiler",
544
- "blockquote",
545
- "br",
546
- ],
547
- allowedAttributes: {
548
- a: ["href"],
549
- },
550
- transformTags: {
551
- h1: "",
552
- h2: "",
553
- h3: "",
554
- h4: "",
555
- h5: "",
556
- h6: "",
557
- a: "",
558
- strong: "",
559
- em: "",
560
- p: () => "",
561
- ul: () => "",
562
- li: () => "• ",
563
- ol: () => "",
564
- hr: () => "\n",
565
- br: () => "\n",
566
- div: () => "",
567
- },
568
- });
569
- return telegramHtml.replaceAll(/\n[\s\n]*\n/g, "\n").trim();
570
- };
571
-
572
- /**
573
- * Private service for processing structured outline completions.
574
- *
575
- * Handles the core logic for executing outline-based AI completions with schema validation.
576
- * Processes AI responses through the agent-swarm-kit json function to extract and validate
577
- * structured trading signal data.
578
- *
579
- * Key features:
580
- * - JSON schema validation using agent-swarm-kit
581
- * - Trading signal extraction and transformation
582
- * - Type conversion for numeric fields
583
- * - Markdown formatting cleanup for notes
584
- * - Error handling for validation failures
585
- *
586
- * @example
587
- * ```typescript
588
- * const outlinePrivate = inject<OutlinePrivateService>(TYPES.outlinePrivateService);
589
- * const signal = await outlinePrivate.getCompletion([
590
- * { role: "user", content: "Analyze market" }
591
- * ]);
592
- * ```
593
- */
594
- class OutlinePrivateService {
595
- constructor() {
596
- /** Logger service for operation tracking */
597
- this.loggerService = inject(TYPES.loggerService);
598
- /**
599
- * Processes outline completion messages and extracts structured signal data.
600
- *
601
- * Sends messages to the AI provider, validates the response against the signal schema,
602
- * and transforms the data into a structured format. Returns null if the AI decides
603
- * to wait (no position).
604
- *
605
- * @param messages - Array of conversation messages for the AI
606
- * @returns Promise resolving to structured signal data or null if position is "wait"
607
- * @throws Error if validation fails or AI returns an error
608
- *
609
- * @example
610
- * ```typescript
611
- * const signal = await outlinePrivateService.getCompletion([
612
- * { role: "system", content: "Trading analyst role" },
613
- * { role: "user", content: "Market analysis data..." }
614
- * ]);
615
- *
616
- * if (signal) {
617
- * console.log(`Position: ${signal.position}`);
618
- * console.log(`Entry: ${signal.priceOpen}`);
619
- * console.log(`SL: ${signal.priceStopLoss}`);
620
- * console.log(`TP: ${signal.priceTakeProfit}`);
621
- * }
622
- * ```
623
- */
624
- this.getCompletion = async (messages) => {
625
- this.loggerService.log("outlinePrivateService getCompletion", {
626
- messages,
627
- });
628
- const { data, resultId, error } = await json(OutlineName$1.SignalOutline, messages);
629
- if (error) {
630
- throw new Error(error);
631
- }
632
- if (data.position === "wait") {
633
- return null;
634
- }
635
- return {
636
- id: resultId,
637
- position: data.position,
638
- minuteEstimatedTime: +data.minute_estimated_time,
639
- priceStopLoss: +data.price_stop_loss,
640
- priceTakeProfit: +data.price_take_profit,
641
- note: await toPlainString(data.risk_note),
642
- priceOpen: data.price_open ? +data.price_open : undefined,
643
- };
644
- };
645
- }
646
- }
647
-
648
499
  /**
649
500
  * Private service managing AI inference provider registry and execution.
650
501
  *
@@ -765,47 +616,19 @@ class RunnerPrivateService {
765
616
  }
766
617
  }
767
618
 
768
- const require = createRequire(import.meta.url);
769
- /**
770
- * Default fallback prompt configuration.
771
- * Used when signal.prompt.cjs file is not found.
772
- */
773
- const DEFAULT_PROMPT = {
774
- user: "",
775
- system: [],
776
- };
777
- /**
778
- * Lazy-loads and caches signal prompt configuration.
779
- * Attempts to load from config/prompt/signal.prompt.cjs, falls back to DEFAULT_PROMPT if not found.
780
- * Uses singleshot pattern to ensure configuration is loaded only once.
781
- * @returns Prompt configuration with system and user prompts
782
- */
783
- const GET_PROMPT_FN = singleshot(() => {
784
- try {
785
- const modulePath = require.resolve(path.join(process.cwd(), `./config/prompt/signal.prompt.cjs`));
786
- console.log(`Using ${modulePath} implementation as signal.prompt.cjs`);
787
- return require(modulePath);
788
- }
789
- catch (error) {
790
- console.log(`Using empty fallback for signal.prompt.cjs`, error);
791
- return DEFAULT_PROMPT;
792
- }
793
- });
794
619
  /**
795
620
  * Service for managing signal prompts for AI/LLM integrations.
796
621
  *
797
- * Provides access to system and user prompts configured in signal.prompt.cjs.
622
+ * Provides access to system and user prompts from Prompt.
798
623
  * Supports both static prompt arrays and dynamic prompt functions.
799
624
  *
800
625
  * Key responsibilities:
801
- * - Lazy-loads prompt configuration from config/prompt/signal.prompt.cjs
802
626
  * - Resolves system prompts (static arrays or async functions)
803
627
  * - Provides user prompt strings
804
- * - Falls back to empty prompts if configuration is missing
805
628
  *
806
629
  * Used for AI-powered signal analysis and strategy recommendations.
807
630
  */
808
- class SignalPromptService {
631
+ class ResolvePromptService {
809
632
  constructor() {
810
633
  this.loggerService = inject(TYPES.loggerService);
811
634
  /**
@@ -816,6 +639,7 @@ class SignalPromptService {
816
639
  * - Async/sync function returning string array (executed and awaited)
817
640
  * - Undefined (returns empty array)
818
641
  *
642
+ * @param prompt - Prompt containing the loaded module
819
643
  * @param symbol - Trading symbol (e.g., "BTCUSDT")
820
644
  * @param strategyName - Strategy identifier
821
645
  * @param exchangeName - Exchange identifier
@@ -823,15 +647,15 @@ class SignalPromptService {
823
647
  * @param backtest - Whether running in backtest mode
824
648
  * @returns Promise resolving to array of system prompt strings
825
649
  */
826
- this.getSystemPrompt = async (symbol, strategyName, exchangeName, frameName, backtest) => {
827
- this.loggerService.log("signalPromptService getSystemPrompt", {
650
+ this.getSystemPrompt = async (prompt, symbol, strategyName, exchangeName, frameName, backtest) => {
651
+ this.loggerService.log("resolvePromptService getSystemPrompt", {
828
652
  symbol,
829
653
  strategyName,
830
654
  exchangeName,
831
655
  frameName,
832
656
  backtest,
833
657
  });
834
- const { system } = GET_PROMPT_FN();
658
+ const { system } = prompt.source;
835
659
  if (Array.isArray(system)) {
836
660
  return system;
837
661
  }
@@ -843,6 +667,7 @@ class SignalPromptService {
843
667
  /**
844
668
  * Retrieves user prompt string for AI input.
845
669
  *
670
+ * @param prompt - Prompt containing the loaded module
846
671
  * @param symbol - Trading symbol (e.g., "BTCUSDT")
847
672
  * @param strategyName - Strategy identifier
848
673
  * @param exchangeName - Exchange identifier
@@ -850,15 +675,15 @@ class SignalPromptService {
850
675
  * @param backtest - Whether running in backtest mode
851
676
  * @returns Promise resolving to user prompt string
852
677
  */
853
- this.getUserPrompt = async (symbol, strategyName, exchangeName, frameName, backtest) => {
854
- this.loggerService.log("signalPromptService getUserPrompt", {
678
+ this.getUserPrompt = async (prompt, symbol, strategyName, exchangeName, frameName, backtest) => {
679
+ this.loggerService.log("resolvePromptService getUserPrompt", {
855
680
  symbol,
856
681
  strategyName,
857
682
  exchangeName,
858
683
  frameName,
859
684
  backtest,
860
685
  });
861
- const { user } = GET_PROMPT_FN();
686
+ const { user } = prompt.source;
862
687
  if (typeof user === "string") {
863
688
  return user;
864
689
  }
@@ -870,92 +695,6 @@ class SignalPromptService {
870
695
  }
871
696
  }
872
697
 
873
- /**
874
- * Public-facing service for structured AI outline completions.
875
- *
876
- * Provides a simplified interface for executing structured AI completions with schema validation.
877
- * Handles context creation and isolation for outline-based operations.
878
- * Used for extracting structured data from AI responses (e.g., trading signals).
879
- *
880
- * Key features:
881
- * - Simplified API with automatic context management
882
- * - JSON schema validation for structured outputs
883
- * - Support for multiple AI providers
884
- * - Optional API key parameter with fallback
885
- * - Logging integration
886
- *
887
- * @example
888
- * ```typescript
889
- * import { engine } from "./lib";
890
- * import { InferenceName } from "./enum/InferenceName";
891
- *
892
- * const signal = await engine.outlinePublicService.getCompletion(
893
- * [{ role: "user", content: "Analyze BTC/USDT and decide position" }],
894
- * InferenceName.ClaudeInference,
895
- * "claude-3-5-sonnet-20240620",
896
- * "sk-ant-..."
897
- * );
898
- *
899
- * // Returns structured signal:
900
- * // {
901
- * // position: "long",
902
- * // priceOpen: 50000,
903
- * // priceStopLoss: 48000,
904
- * // priceTakeProfit: 52000,
905
- * // minuteEstimatedTime: 120,
906
- * // note: "Strong bullish momentum..."
907
- * // }
908
- * ```
909
- */
910
- class OutlinePublicService {
911
- constructor() {
912
- /** Logger service for operation tracking */
913
- this.loggerService = inject(TYPES.loggerService);
914
- /** Private service handling outline completion logic */
915
- this.outlinePrivateService = inject(TYPES.outlinePrivateService);
916
- /**
917
- * Executes a structured outline completion with schema validation.
918
- *
919
- * Creates an isolated execution context and processes messages through the AI provider
920
- * to generate a structured response conforming to a predefined schema.
921
- *
922
- * @param messages - Array of conversation messages for the AI
923
- * @param inference - AI provider identifier
924
- * @param model - Model name/identifier
925
- * @param apiKey - Optional API key(s), required for most providers
926
- * @returns Promise resolving to structured signal data or null if position is "wait"
927
- *
928
- * @example
929
- * ```typescript
930
- * const result = await outlinePublicService.getCompletion(
931
- * [
932
- * { role: "system", content: "You are a trading analyst" },
933
- * { role: "user", content: "Analyze current BTC market" }
934
- * ],
935
- * InferenceName.DeepseekInference,
936
- * "deepseek-chat",
937
- * "sk-..."
938
- * );
939
- * ```
940
- */
941
- this.getCompletion = async (messages, inference, model, apiKey) => {
942
- this.loggerService.log("outlinePublicService getCompletion", {
943
- messages,
944
- model,
945
- apiKey,
946
- inference,
947
- });
948
- return await ContextService.runInContext(async () => {
949
- return await this.outlinePrivateService.getCompletion(messages);
950
- }, {
951
- apiKey: apiKey,
952
- inference,
953
- model,
954
- });
955
- };
956
- }
957
- }
958
-
959
698
  /**
960
699
  * Public-facing service for AI inference operations with context management.
961
700
  *
@@ -1093,6 +832,106 @@ class RunnerPublicService {
1093
832
  }
1094
833
  }
1095
834
 
835
+ /**
836
+ * Lints and auto-fixes markdown content using markdownlint rules.
837
+ *
838
+ * Validates markdown syntax and applies automatic fixes for common issues
839
+ * like inconsistent list markers, trailing spaces, and heading styles.
840
+ * Returns the original content if no errors found or fixes cannot be applied.
841
+ *
842
+ * @param content - Raw markdown content to lint
843
+ * @returns Promise resolving to linted markdown content
844
+ *
845
+ * @example
846
+ * ```typescript
847
+ * const markdown = "# Title\n\n\n## Subtitle"; // Multiple blank lines
848
+ * const linted = await toLintMarkdown(markdown);
849
+ * // Returns: "# Title\n\n## Subtitle" (extra blank line removed)
850
+ * ```
851
+ */
852
+ const toLintMarkdown = async (content) => {
853
+ if (!content) {
854
+ return "";
855
+ }
856
+ const { content: errors } = await lint({ strings: { content } });
857
+ if (!errors.length) {
858
+ return content;
859
+ }
860
+ const value = applyFixes(content, errors);
861
+ return value ? value : content;
862
+ };
863
+
864
+ /**
865
+ * Converts markdown content to plain text with Telegram-compatible HTML formatting.
866
+ *
867
+ * Processes markdown through three stages:
868
+ * 1. Lints and fixes markdown using markdownlint
869
+ * 2. Renders markdown to HTML using markdown-it
870
+ * 3. Sanitizes HTML to Telegram-compatible subset
871
+ *
872
+ * Supported tags: b, i, a, code, pre, s, u, tg-spoiler, blockquote, br
873
+ * Transforms: headings removed, lists to bullets, multiple newlines collapsed
874
+ *
875
+ * @param content - Raw markdown content
876
+ * @returns Promise resolving to sanitized plain text with HTML formatting
877
+ *
878
+ * @example
879
+ * ```typescript
880
+ * const markdown = "# Title\n**Bold** and *italic*\n- Item 1\n- Item 2";
881
+ * const plain = await toPlainString(markdown);
882
+ * // Returns: "Bold and italic\n• Item 1\n• Item 2"
883
+ * ```
884
+ */
885
+ const toPlainString = async (content) => {
886
+ if (!content) {
887
+ return "";
888
+ }
889
+ const markdown = await toLintMarkdown(content);
890
+ const md = new MarkdownIt({
891
+ html: false,
892
+ breaks: true,
893
+ linkify: true,
894
+ typographer: true,
895
+ });
896
+ let telegramHtml = md.render(markdown);
897
+ telegramHtml = sanitizeHtml(telegramHtml, {
898
+ allowedTags: [
899
+ "b",
900
+ "i",
901
+ "a",
902
+ "code",
903
+ "pre",
904
+ "s",
905
+ "u",
906
+ "tg-spoiler",
907
+ "blockquote",
908
+ "br",
909
+ ],
910
+ allowedAttributes: {
911
+ a: ["href"],
912
+ },
913
+ transformTags: {
914
+ h1: "",
915
+ h2: "",
916
+ h3: "",
917
+ h4: "",
918
+ h5: "",
919
+ h6: "",
920
+ a: "",
921
+ strong: "",
922
+ em: "",
923
+ p: () => "",
924
+ ul: () => "",
925
+ li: () => "• ",
926
+ ol: () => "",
927
+ hr: () => "\n",
928
+ br: () => "\n",
929
+ div: () => "",
930
+ },
931
+ });
932
+ return telegramHtml.replaceAll(/\n[\s\n]*\n/g, "\n").trim();
933
+ };
934
+
1096
935
  /**
1097
936
  * Default template service for generating optimizer code snippets.
1098
937
  * Implements all IOptimizerTemplate methods with Ollama LLM integration.
@@ -2467,46 +2306,62 @@ class OptimizerGlobalService {
2467
2306
  * This file is imported by lib/index.ts to ensure services are registered
2468
2307
  * before the DI container is initialized.
2469
2308
  */
2470
- /**
2471
- * Register common services.
2472
- */
2473
- {
2474
- provide(TYPES.loggerService, () => new LoggerService());
2475
- }
2476
2309
  /**
2477
2310
  * Register base services.
2478
2311
  */
2479
2312
  {
2480
2313
  provide(TYPES.contextService, () => new ContextService());
2314
+ provide(TYPES.loggerService, () => new LoggerService());
2481
2315
  }
2482
2316
  /**
2483
2317
  * Register private services.
2484
2318
  */
2485
2319
  {
2486
2320
  provide(TYPES.runnerPrivateService, () => new RunnerPrivateService());
2487
- provide(TYPES.outlinePrivateService, () => new OutlinePrivateService());
2488
2321
  }
2489
2322
  /**
2490
2323
  * Register public services.
2491
2324
  */
2492
2325
  {
2493
2326
  provide(TYPES.runnerPublicService, () => new RunnerPublicService());
2494
- provide(TYPES.outlinePublicService, () => new OutlinePublicService());
2495
2327
  }
2496
2328
  {
2497
- provide(TYPES.signalPromptService, () => new SignalPromptService());
2329
+ provide(TYPES.resolvePromptService, () => new ResolvePromptService());
2330
+ }
2331
+ {
2332
+ provide(TYPES.promptCacheService, () => new PromptCacheService());
2498
2333
  }
2499
2334
  {
2500
2335
  provide(TYPES.outlineMarkdownService, () => new OutlineMarkdownService());
2501
2336
  }
2502
2337
  /**
2503
- * Register optimizer services.
2338
+ * Register template services.
2504
2339
  */
2505
2340
  {
2506
2341
  provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
2342
+ }
2343
+ /**
2344
+ * Register schema services.
2345
+ */
2346
+ {
2507
2347
  provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
2348
+ }
2349
+ /**
2350
+ * Register validation services.
2351
+ */
2352
+ {
2508
2353
  provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
2354
+ }
2355
+ /**
2356
+ * Register connection services.
2357
+ */
2358
+ {
2509
2359
  provide(TYPES.optimizerConnectionService, () => new OptimizerConnectionService());
2360
+ }
2361
+ /**
2362
+ * Register global services.
2363
+ */
2364
+ {
2510
2365
  provide(TYPES.optimizerGlobalService, () => new OptimizerGlobalService());
2511
2366
  }
2512
2367
 
@@ -6483,43 +6338,47 @@ class GLM4Provider {
6483
6338
  * );
6484
6339
  * ```
6485
6340
  */
6486
- /**
6487
- * Common service instances.
6488
- */
6489
- const commonServices = {
6490
- loggerService: inject(TYPES.loggerService),
6491
- };
6492
6341
  /**
6493
6342
  * Base service instances.
6494
6343
  */
6495
6344
  const baseServices = {
6496
6345
  contextService: inject(TYPES.contextService),
6346
+ loggerService: inject(TYPES.loggerService),
6497
6347
  };
6498
6348
  /**
6499
6349
  * Private service instances.
6500
6350
  */
6501
6351
  const privateServices = {
6502
6352
  runnerPrivateService: inject(TYPES.runnerPrivateService),
6503
- outlinePrivateService: inject(TYPES.outlinePrivateService),
6504
6353
  };
6505
6354
  /**
6506
6355
  * Public service instances.
6507
6356
  */
6508
6357
  const publicServices = {
6509
6358
  runnerPublicService: inject(TYPES.runnerPublicService),
6510
- outlinePublicService: inject(TYPES.outlinePublicService),
6511
6359
  };
6512
6360
  const promptServices = {
6513
- signalPromptService: inject(TYPES.signalPromptService),
6361
+ resolvePromptService: inject(TYPES.resolvePromptService),
6362
+ };
6363
+ const cacheServices = {
6364
+ promptCacheService: inject(TYPES.promptCacheService),
6514
6365
  };
6515
6366
  const markdownServices = {
6516
6367
  outlineMarkdownService: inject(TYPES.outlineMarkdownService),
6517
6368
  };
6518
- const optimizerServices = {
6369
+ const templateServices = {
6519
6370
  optimizerTemplateService: inject(TYPES.optimizerTemplateService),
6371
+ };
6372
+ const schemaServices = {
6520
6373
  optimizerSchemaService: inject(TYPES.optimizerSchemaService),
6374
+ };
6375
+ const validationServices = {
6521
6376
  optimizerValidationService: inject(TYPES.optimizerValidationService),
6377
+ };
6378
+ const connectionServices = {
6522
6379
  optimizerConnectionService: inject(TYPES.optimizerConnectionService),
6380
+ };
6381
+ const globalServices = {
6523
6382
  optimizerGlobalService: inject(TYPES.optimizerGlobalService),
6524
6383
  };
6525
6384
  /**
@@ -6527,13 +6386,17 @@ const optimizerServices = {
6527
6386
  * Provides unified access to the entire service layer.
6528
6387
  */
6529
6388
  const engine = {
6530
- ...commonServices,
6531
6389
  ...baseServices,
6532
6390
  ...privateServices,
6533
6391
  ...publicServices,
6534
6392
  ...promptServices,
6393
+ ...cacheServices,
6535
6394
  ...markdownServices,
6536
- ...optimizerServices,
6395
+ ...templateServices,
6396
+ ...schemaServices,
6397
+ ...validationServices,
6398
+ ...connectionServices,
6399
+ ...globalServices,
6537
6400
  };
6538
6401
  // Initialize DI container
6539
6402
  init();
@@ -6685,446 +6548,356 @@ addCompletion({
6685
6548
  });
6686
6549
 
6687
6550
  /**
6688
- * Zod schema for trading signal structured output.
6551
+ * Wrap async function with Ollama inference context.
6689
6552
  *
6690
- * Defines the JSON schema used for LLM-generated trading signals with
6691
- * comprehensive field descriptions and validation rules. Used with outline
6692
- * completion to enforce structured output from language models.
6553
+ * Creates a higher-order function that executes the provided async function
6554
+ * within an Ollama inference context. Supports token rotation by passing multiple API keys.
6693
6555
  *
6694
- * Fields:
6695
- * - position: Trading direction (long/short/wait)
6696
- * - price_open: Entry price in USD
6697
- * - price_stop_loss: Stop-loss price in USD
6698
- * - price_take_profit: Take-profit price in USD
6699
- * - minute_estimated_time: Estimated hold duration in minutes
6700
- * - risk_note: Detailed risk assessment with specific metrics
6701
- *
6702
- * @example
6703
- * ```typescript
6704
- * import { SignalSchema } from './schema/Signal.schema';
6705
- *
6706
- * const signal = SignalSchema.parse({
6707
- * position: 'long',
6708
- * price_open: 50000,
6709
- * price_stop_loss: 49000,
6710
- * price_take_profit: 52000,
6711
- * minute_estimated_time: 120,
6712
- * risk_note: 'RSI oversold at 32%, volume spike +45%'
6713
- * });
6714
- * ```
6715
- */
6716
- const SignalSchema = z.object({
6717
- position: z
6718
- .enum(["long", "short", "wait"])
6719
- .describe(str.newline("Position direction (ALWAYS required):", "long: market shows consistent bullish signals, uptrend or growth potential", "short: market shows consistent bearish signals, downtrend or decline potential", "wait: conflicting signals between timeframes OR unfavorable trading conditions")),
6720
- price_open: z
6721
- .number()
6722
- .describe(str.newline("Position entry price in USD", "Can be either:", "- Current market price for immediate entry (market order)", "- Price above/below market to open a limit order and wait for its resolve before entering")),
6723
- price_stop_loss: z
6724
- .number()
6725
- .describe(str.newline("Stop-loss price in USD", "For LONG: price below price_open (protection against decline)", "For SHORT: price above price_open (protection against rise)", "NEVER set SL in 'empty space' without technical justification")),
6726
- price_take_profit: z
6727
- .number()
6728
- .describe(str.newline("Take-profit price in USD", "For LONG: price above price_open (growth target)", "For SHORT: price below price_open (decline target)", "NEVER set TP based on trend without technical justification")),
6729
- minute_estimated_time: z
6730
- .number()
6731
- .describe(str.newline("Estimated time to reach Take Profit in minutes", "Calculated based on HONEST technical analysis, using:", "ATR, ADX, MACD, Momentum, Slope and other metrics")),
6732
- risk_note: z
6733
- .string()
6734
- .describe(str.newline("Description of current market situation risks:", "", "Analyze and specify applicable risks:", "1. Whale manipulations (volume spikes, long shadows, pin bars, candle engulfing, false breakouts)", "2. Order book (order book walls, spoofing, bid/ask imbalance, low liquidity)", "3. P&L history (recurring mistakes on similar patterns)", "4. Time factors (trading session, low liquidity, upcoming events)", "5. Correlations (overall market trend, conflicting trends across timeframes)", "6. Technical risks (indicator divergences, weak volumes, critical levels)", "7. Gaps and anomalies (price gaps, unfilled gaps, movements without volume)", "", "Provide SPECIFIC numbers, percentages and probabilities.")),
6735
- });
6736
-
6737
- /**
6738
- * Trading signal outline schema registration.
6739
- *
6740
- * Registers a structured outline for trading signal generation with comprehensive
6741
- * validation rules. This outline enforces a strict schema for AI-generated trading
6742
- * signals, ensuring all required fields are present and correctly formatted.
6743
- *
6744
- * Schema fields:
6745
- * - position: Trading direction ("long", "short", or "wait")
6746
- * - price_open: Entry price for the position
6747
- * - price_stop_loss: Stop-loss price level
6748
- * - price_take_profit: Take-profit price level
6749
- * - minute_estimated_time: Estimated time to reach TP (in minutes)
6750
- * - risk_note: Risk assessment and reasoning (markdown format)
6751
- *
6752
- * Validation rules:
6753
- * 1. All required fields must be present
6754
- * 2. Prices must be positive numbers
6755
- * 3. For LONG: SL < entry < TP
6756
- * 4. For SHORT: TP < entry < SL
6757
- * 5. Estimated time must be <= 360 minutes (6 hours)
6758
- * 6. Wait position skips price validations
6759
- *
6760
- * @example
6761
- * ```typescript
6762
- * import { json } from "agent-swarm-kit";
6763
- * import { OutlineName } from "./enum/OutlineName";
6764
- *
6765
- * const { data } = await json(OutlineName.SignalOutline, [
6766
- * { role: "user", content: "Analyze BTC/USDT and decide position" }
6767
- * ]);
6768
- *
6769
- * if (data.position !== "wait") {
6770
- * console.log(`Position: ${data.position}`);
6771
- * console.log(`Entry: ${data.price_open}`);
6772
- * console.log(`SL: ${data.price_stop_loss}`);
6773
- * console.log(`TP: ${data.price_take_profit}`);
6774
- * }
6775
- * ```
6776
- */
6777
- addOutline({
6778
- outlineName: OutlineName.SignalOutline,
6779
- completion: CompletionName.RunnerOutlineCompletion,
6780
- format: zodResponseFormat(SignalSchema, "position_open_decision"),
6781
- getOutlineHistory: async ({ history, param: messages = [] }) => {
6782
- await history.push(messages);
6783
- },
6784
- validations: [
6785
- {
6786
- validate: ({ data }) => {
6787
- if (!data.position) {
6788
- throw new Error("The position field is not filled");
6789
- }
6790
- },
6791
- docDescription: "Validates that position direction (long/short/wait) is specified.",
6792
- },
6793
- {
6794
- validate: ({ data }) => {
6795
- if (!data.risk_note) {
6796
- throw new Error("The risk_note field is not filled");
6797
- }
6798
- },
6799
- docDescription: "Validates that risk description is provided.",
6800
- },
6801
- {
6802
- validate: ({ data }) => {
6803
- if (data.position !== "wait" &&
6804
- (!data.price_open || data.price_open <= 0)) {
6805
- throw new Error("When position='long' or 'short', the price_open field is required and must be positive");
6806
- }
6807
- },
6808
- docDescription: "Validates that opening price is specified and positive when opening a position.",
6809
- },
6810
- {
6811
- validate: ({ data }) => {
6812
- if (data.position !== "wait" &&
6813
- (!data.price_stop_loss || data.price_stop_loss <= 0)) {
6814
- throw new Error("When position='long' or 'short', the price_stop_loss field is required and must be positive");
6815
- }
6816
- },
6817
- docDescription: "Validates that stop-loss is specified when opening a position.",
6818
- },
6819
- {
6820
- validate: ({ data }) => {
6821
- if (data.position !== "wait" &&
6822
- (!data.price_take_profit || data.price_take_profit <= 0)) {
6823
- throw new Error("When position='long' or 'short', the price_take_profit field is required and must be positive");
6824
- }
6825
- },
6826
- docDescription: "Validates that take-profit is specified when opening a position.",
6827
- },
6828
- {
6829
- validate: ({ data }) => {
6830
- if (data.position === "long" && data.price_open && data.price_stop_loss && data.price_take_profit) {
6831
- if (data.price_stop_loss >= data.price_open) {
6832
- throw new Error("For LONG position, price_stop_loss must be below price_open");
6833
- }
6834
- if (data.price_take_profit <= data.price_open) {
6835
- throw new Error("For LONG position, price_take_profit must be above price_open");
6836
- }
6837
- }
6838
- },
6839
- docDescription: "Validates price correctness for LONG position.",
6840
- },
6841
- {
6842
- validate: ({ data }) => {
6843
- if (data.position === "short" && data.price_open && data.price_stop_loss && data.price_take_profit) {
6844
- if (data.price_stop_loss <= data.price_open) {
6845
- throw new Error("For SHORT position, price_stop_loss must be above price_open");
6846
- }
6847
- if (data.price_take_profit >= data.price_open) {
6848
- throw new Error("For SHORT position, price_take_profit must be below price_open");
6849
- }
6850
- }
6851
- },
6852
- docDescription: "Validates price correctness for SHORT position.",
6853
- },
6854
- {
6855
- validate: ({ data }) => {
6856
- if (data.position !== "wait" &&
6857
- (!data.minute_estimated_time || data.minute_estimated_time <= 0)) {
6858
- throw new Error("When position='long' or 'short', the minute_estimated_time field is required and must be positive");
6859
- }
6860
- },
6861
- docDescription: "Validates that estimated time to TP is specified when opening a position.",
6862
- },
6863
- {
6864
- validate: ({ data }) => {
6865
- if (data.position !== "wait" && data.minute_estimated_time > 360) {
6866
- throw new Error("Estimated time to reach TP exceeds 6 hours (360 minutes). Use position='wait' for low volatility conditions");
6867
- }
6868
- },
6869
- docDescription: "Validates that estimated time to reach TP does not exceed 6 hours.",
6870
- },
6871
- ],
6872
- });
6873
-
6874
- /**
6875
- * Bootstrap module for agent-swarm-kit validation.
6876
- *
6877
- * Validates that all completion and outline names are properly registered
6878
- * with agent-swarm-kit before the application starts. This ensures that
6879
- * all referenced completions and outlines exist and are correctly configured.
6880
- *
6881
- * Validation checks:
6882
- * - All CompletionName enum values have corresponding registered handlers
6883
- * - All OutlineName enum values have corresponding registered schemas
6884
- * - No duplicate registrations exist
6885
- *
6886
- * This file is imported by index.ts to run validation at startup.
6887
- *
6888
- * @throws Error if validation fails (missing or duplicate registrations)
6889
- */
6890
- validate$1({
6891
- CompletionName: CompletionName$1,
6892
- OutlineName: OutlineName$1,
6893
- });
6894
-
6895
- /**
6896
- * Generate structured trading signal from Ollama models.
6897
- *
6898
- * Supports token rotation by passing multiple API keys. Automatically enforces
6899
- * the signal JSON schema defined in Signal.schema.ts.
6900
- *
6901
- * @param messages - Array of outline messages (user/assistant/system)
6556
+ * @template T - Async function type
6557
+ * @param fn - Async function to wrap
6902
6558
  * @param model - Ollama model name (e.g., "llama3.3:70b")
6903
6559
  * @param apiKey - Single API key or array of keys for rotation
6904
- * @returns Promise resolving to structured trading signal
6560
+ * @returns Wrapped function with same signature as input
6905
6561
  *
6906
6562
  * @example
6907
6563
  * ```typescript
6908
6564
  * import { ollama } from '@backtest-kit/ollama';
6909
6565
  *
6910
- * const signal = await ollama(messages, 'llama3.3:70b', ['key1', 'key2']);
6911
- * console.log(signal.position); // "long" | "short" | "wait"
6566
+ * const wrappedFn = ollama(myAsyncFn, 'llama3.3:70b', ['key1', 'key2']);
6567
+ * const result = await wrappedFn(args);
6912
6568
  * ```
6913
6569
  */
6914
- const ollama = async (messages, model, apiKey) => {
6915
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.OllamaInference, model, apiKey);
6570
+ const ollama = (fn, model, apiKey) => {
6571
+ const wrappedFn = async (args) => {
6572
+ return await ContextService.runInContext(async () => {
6573
+ return await fn(...args);
6574
+ }, {
6575
+ apiKey,
6576
+ inference: InferenceName$1.OllamaInference,
6577
+ model,
6578
+ });
6579
+ };
6580
+ return wrappedFn;
6916
6581
  };
6917
6582
  /**
6918
- * Generate structured trading signal from Grok models.
6583
+ * Wrap async function with Grok inference context.
6919
6584
  *
6920
- * Uses xAI Grok models through direct API access. Does NOT support token rotation.
6585
+ * Creates a higher-order function that executes the provided async function
6586
+ * within a Grok (xAI) inference context.
6921
6587
  *
6922
- * @param messages - Array of outline messages (user/assistant/system)
6588
+ * @template T - Async function type
6589
+ * @param fn - Async function to wrap
6923
6590
  * @param model - Grok model name (e.g., "grok-beta")
6924
- * @param apiKey - Single API key (token rotation not supported)
6925
- * @returns Promise resolving to structured trading signal
6926
- * @throws Error if apiKey is an array (token rotation not supported)
6591
+ * @param apiKey - Single API key or array of keys
6592
+ * @returns Wrapped function with same signature as input
6927
6593
  *
6928
6594
  * @example
6929
6595
  * ```typescript
6930
6596
  * import { grok } from '@backtest-kit/ollama';
6931
6597
  *
6932
- * const signal = await grok(messages, 'grok-beta', process.env.GROK_API_KEY);
6598
+ * const wrappedFn = grok(myAsyncFn, 'grok-beta', process.env.GROK_API_KEY);
6599
+ * const result = await wrappedFn(args);
6933
6600
  * ```
6934
6601
  */
6935
- const grok = async (messages, model, apiKey) => {
6936
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.GrokInference, model, apiKey);
6602
+ const grok = (fn, model, apiKey) => {
6603
+ const wrappedFn = async (args) => {
6604
+ return await ContextService.runInContext(async () => {
6605
+ return await fn(...args);
6606
+ }, {
6607
+ apiKey,
6608
+ inference: InferenceName$1.GrokInference,
6609
+ model,
6610
+ });
6611
+ };
6612
+ return wrappedFn;
6937
6613
  };
6938
6614
  /**
6939
- * Generate structured trading signal from Hugging Face models.
6615
+ * Wrap async function with HuggingFace inference context.
6940
6616
  *
6941
- * Uses HuggingFace Router API for model access. Does NOT support token rotation.
6617
+ * Creates a higher-order function that executes the provided async function
6618
+ * within a HuggingFace Router API inference context.
6942
6619
  *
6943
- * @param messages - Array of outline messages (user/assistant/system)
6944
- * @param model - HuggingFace model name
6945
- * @param apiKey - Single API key (token rotation not supported)
6946
- * @returns Promise resolving to structured trading signal
6620
+ * @template T - Async function type
6621
+ * @param fn - Async function to wrap
6622
+ * @param model - HuggingFace model name (e.g., "meta-llama/Llama-3-70b")
6623
+ * @param apiKey - Single API key or array of keys
6624
+ * @returns Wrapped function with same signature as input
6947
6625
  *
6948
6626
  * @example
6949
6627
  * ```typescript
6950
6628
  * import { hf } from '@backtest-kit/ollama';
6951
6629
  *
6952
- * const signal = await hf(messages, 'meta-llama/Llama-3-70b', process.env.HF_API_KEY);
6630
+ * const wrappedFn = hf(myAsyncFn, 'meta-llama/Llama-3-70b', process.env.HF_API_KEY);
6631
+ * const result = await wrappedFn(args);
6953
6632
  * ```
6954
6633
  */
6955
- const hf = async (messages, model, apiKey) => {
6956
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.HfInference, model, apiKey);
6634
+ const hf = (fn, model, apiKey) => {
6635
+ const wrappedFn = async (args) => {
6636
+ return await ContextService.runInContext(async () => {
6637
+ return await fn(...args);
6638
+ }, {
6639
+ apiKey,
6640
+ inference: InferenceName$1.HfInference,
6641
+ model,
6642
+ });
6643
+ };
6644
+ return wrappedFn;
6957
6645
  };
6958
6646
  /**
6959
- * Generate structured trading signal from Claude models.
6647
+ * Wrap async function with Claude inference context.
6960
6648
  *
6961
- * Uses Anthropic Claude through OpenAI-compatible API. Does NOT support token rotation.
6649
+ * Creates a higher-order function that executes the provided async function
6650
+ * within an Anthropic Claude inference context.
6962
6651
  *
6963
- * @param messages - Array of outline messages (user/assistant/system)
6652
+ * @template T - Async function type
6653
+ * @param fn - Async function to wrap
6964
6654
  * @param model - Claude model name (e.g., "claude-3-5-sonnet-20241022")
6965
- * @param apiKey - Single API key (token rotation not supported)
6966
- * @returns Promise resolving to structured trading signal
6967
- * @throws Error if apiKey is an array (token rotation not supported)
6655
+ * @param apiKey - Single API key or array of keys
6656
+ * @returns Wrapped function with same signature as input
6968
6657
  *
6969
6658
  * @example
6970
6659
  * ```typescript
6971
6660
  * import { claude } from '@backtest-kit/ollama';
6972
6661
  *
6973
- * const signal = await claude(messages, 'claude-3-5-sonnet-20241022', process.env.ANTHROPIC_API_KEY);
6662
+ * const wrappedFn = claude(myAsyncFn, 'claude-3-5-sonnet-20241022', process.env.ANTHROPIC_API_KEY);
6663
+ * const result = await wrappedFn(args);
6974
6664
  * ```
6975
6665
  */
6976
- const claude = async (messages, model, apiKey) => {
6977
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.ClaudeInference, model, apiKey);
6666
+ const claude = (fn, model, apiKey) => {
6667
+ const wrappedFn = async (args) => {
6668
+ return await ContextService.runInContext(async () => {
6669
+ return await fn(...args);
6670
+ }, {
6671
+ apiKey,
6672
+ inference: InferenceName$1.ClaudeInference,
6673
+ model,
6674
+ });
6675
+ };
6676
+ return wrappedFn;
6978
6677
  };
6979
6678
  /**
6980
- * Generate structured trading signal from OpenAI GPT models.
6679
+ * Wrap async function with OpenAI GPT inference context.
6981
6680
  *
6982
- * Uses official OpenAI SDK with JSON schema enforcement. Does NOT support token rotation.
6681
+ * Creates a higher-order function that executes the provided async function
6682
+ * within an OpenAI GPT inference context.
6983
6683
  *
6984
- * @param messages - Array of outline messages (user/assistant/system)
6684
+ * @template T - Async function type
6685
+ * @param fn - Async function to wrap
6985
6686
  * @param model - OpenAI model name (e.g., "gpt-4o", "gpt-4-turbo")
6986
- * @param apiKey - Single API key (token rotation not supported)
6987
- * @returns Promise resolving to structured trading signal
6988
- * @throws Error if apiKey is an array (token rotation not supported)
6687
+ * @param apiKey - Single API key or array of keys
6688
+ * @returns Wrapped function with same signature as input
6989
6689
  *
6990
6690
  * @example
6991
6691
  * ```typescript
6992
6692
  * import { gpt5 } from '@backtest-kit/ollama';
6993
6693
  *
6994
- * const signal = await gpt5(messages, 'gpt-4o', process.env.OPENAI_API_KEY);
6694
+ * const wrappedFn = gpt5(myAsyncFn, 'gpt-4o', process.env.OPENAI_API_KEY);
6695
+ * const result = await wrappedFn(args);
6995
6696
  * ```
6996
6697
  */
6997
- const gpt5 = async (messages, model, apiKey) => {
6998
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.GPT5Inference, model, apiKey);
6698
+ const gpt5 = (fn, model, apiKey) => {
6699
+ const wrappedFn = async (args) => {
6700
+ return await ContextService.runInContext(async () => {
6701
+ return await fn(...args);
6702
+ }, {
6703
+ apiKey,
6704
+ inference: InferenceName$1.GPT5Inference,
6705
+ model,
6706
+ });
6707
+ };
6708
+ return wrappedFn;
6999
6709
  };
7000
6710
  /**
7001
- * Generate structured trading signal from DeepSeek models.
6711
+ * Wrap async function with DeepSeek inference context.
7002
6712
  *
7003
- * Uses DeepSeek AI through OpenAI-compatible API. Does NOT support token rotation.
6713
+ * Creates a higher-order function that executes the provided async function
6714
+ * within a DeepSeek AI inference context.
7004
6715
  *
7005
- * @param messages - Array of outline messages (user/assistant/system)
6716
+ * @template T - Async function type
6717
+ * @param fn - Async function to wrap
7006
6718
  * @param model - DeepSeek model name (e.g., "deepseek-chat")
7007
- * @param apiKey - Single API key (token rotation not supported)
7008
- * @returns Promise resolving to structured trading signal
7009
- * @throws Error if apiKey is an array (token rotation not supported)
6719
+ * @param apiKey - Single API key or array of keys
6720
+ * @returns Wrapped function with same signature as input
7010
6721
  *
7011
6722
  * @example
7012
6723
  * ```typescript
7013
6724
  * import { deepseek } from '@backtest-kit/ollama';
7014
6725
  *
7015
- * const signal = await deepseek(messages, 'deepseek-chat', process.env.DEEPSEEK_API_KEY);
6726
+ * const wrappedFn = deepseek(myAsyncFn, 'deepseek-chat', process.env.DEEPSEEK_API_KEY);
6727
+ * const result = await wrappedFn(args);
7016
6728
  * ```
7017
6729
  */
7018
- const deepseek = async (messages, model, apiKey) => {
7019
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.DeepseekInference, model, apiKey);
6730
+ const deepseek = (fn, model, apiKey) => {
6731
+ const wrappedFn = async (args) => {
6732
+ return await ContextService.runInContext(async () => {
6733
+ return await fn(...args);
6734
+ }, {
6735
+ apiKey,
6736
+ inference: InferenceName$1.DeepseekInference,
6737
+ model,
6738
+ });
6739
+ };
6740
+ return wrappedFn;
7020
6741
  };
7021
6742
  /**
7022
- * Generate structured trading signal from Mistral AI models.
6743
+ * Wrap async function with Mistral AI inference context.
7023
6744
  *
7024
- * Uses Mistral AI through OpenAI-compatible API. Does NOT support token rotation.
6745
+ * Creates a higher-order function that executes the provided async function
6746
+ * within a Mistral AI inference context.
7025
6747
  *
7026
- * @param messages - Array of outline messages (user/assistant/system)
6748
+ * @template T - Async function type
6749
+ * @param fn - Async function to wrap
7027
6750
  * @param model - Mistral model name (e.g., "mistral-large-latest")
7028
- * @param apiKey - Single API key (token rotation not supported)
7029
- * @returns Promise resolving to structured trading signal
7030
- * @throws Error if apiKey is an array (token rotation not supported)
6751
+ * @param apiKey - Single API key or array of keys
6752
+ * @returns Wrapped function with same signature as input
7031
6753
  *
7032
6754
  * @example
7033
6755
  * ```typescript
7034
6756
  * import { mistral } from '@backtest-kit/ollama';
7035
6757
  *
7036
- * const signal = await mistral(messages, 'mistral-large-latest', process.env.MISTRAL_API_KEY);
6758
+ * const wrappedFn = mistral(myAsyncFn, 'mistral-large-latest', process.env.MISTRAL_API_KEY);
6759
+ * const result = await wrappedFn(args);
7037
6760
  * ```
7038
6761
  */
7039
- const mistral = async (messages, model, apiKey) => {
7040
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.MistralInference, model, apiKey);
6762
+ const mistral = (fn, model, apiKey) => {
6763
+ const wrappedFn = async (args) => {
6764
+ return await ContextService.runInContext(async () => {
6765
+ return await fn(...args);
6766
+ }, {
6767
+ apiKey,
6768
+ inference: InferenceName$1.MistralInference,
6769
+ model,
6770
+ });
6771
+ };
6772
+ return wrappedFn;
7041
6773
  };
7042
6774
  /**
7043
- * Generate structured trading signal from Perplexity AI models.
6775
+ * Wrap async function with Perplexity AI inference context.
7044
6776
  *
7045
- * Uses Perplexity AI through OpenAI-compatible API. Does NOT support token rotation.
6777
+ * Creates a higher-order function that executes the provided async function
6778
+ * within a Perplexity AI inference context.
7046
6779
  *
7047
- * @param messages - Array of outline messages (user/assistant/system)
6780
+ * @template T - Async function type
6781
+ * @param fn - Async function to wrap
7048
6782
  * @param model - Perplexity model name (e.g., "llama-3.1-sonar-huge-128k-online")
7049
- * @param apiKey - Single API key (token rotation not supported)
7050
- * @returns Promise resolving to structured trading signal
7051
- * @throws Error if apiKey is an array (token rotation not supported)
6783
+ * @param apiKey - Single API key or array of keys
6784
+ * @returns Wrapped function with same signature as input
7052
6785
  *
7053
6786
  * @example
7054
6787
  * ```typescript
7055
6788
  * import { perplexity } from '@backtest-kit/ollama';
7056
6789
  *
7057
- * const signal = await perplexity(messages, 'llama-3.1-sonar-huge-128k-online', process.env.PERPLEXITY_API_KEY);
6790
+ * const wrappedFn = perplexity(myAsyncFn, 'llama-3.1-sonar-huge-128k-online', process.env.PERPLEXITY_API_KEY);
6791
+ * const result = await wrappedFn(args);
7058
6792
  * ```
7059
6793
  */
7060
- const perplexity = async (messages, model, apiKey) => {
7061
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.PerplexityInference, model, apiKey);
6794
+ const perplexity = (fn, model, apiKey) => {
6795
+ const wrappedFn = async (args) => {
6796
+ return await ContextService.runInContext(async () => {
6797
+ return await fn(...args);
6798
+ }, {
6799
+ apiKey,
6800
+ inference: InferenceName$1.PerplexityInference,
6801
+ model,
6802
+ });
6803
+ };
6804
+ return wrappedFn;
7062
6805
  };
7063
6806
  /**
7064
- * Generate structured trading signal from Cohere models.
6807
+ * Wrap async function with Cohere inference context.
7065
6808
  *
7066
- * Uses Cohere AI through OpenAI-compatible API. Does NOT support token rotation.
6809
+ * Creates a higher-order function that executes the provided async function
6810
+ * within a Cohere AI inference context.
7067
6811
  *
7068
- * @param messages - Array of outline messages (user/assistant/system)
6812
+ * @template T - Async function type
6813
+ * @param fn - Async function to wrap
7069
6814
  * @param model - Cohere model name (e.g., "command-r-plus")
7070
- * @param apiKey - Single API key (token rotation not supported)
7071
- * @returns Promise resolving to structured trading signal
7072
- * @throws Error if apiKey is an array (token rotation not supported)
6815
+ * @param apiKey - Single API key or array of keys
6816
+ * @returns Wrapped function with same signature as input
7073
6817
  *
7074
6818
  * @example
7075
6819
  * ```typescript
7076
6820
  * import { cohere } from '@backtest-kit/ollama';
7077
6821
  *
7078
- * const signal = await cohere(messages, 'command-r-plus', process.env.COHERE_API_KEY);
6822
+ * const wrappedFn = cohere(myAsyncFn, 'command-r-plus', process.env.COHERE_API_KEY);
6823
+ * const result = await wrappedFn(args);
7079
6824
  * ```
7080
6825
  */
7081
- const cohere = async (messages, model, apiKey) => {
7082
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.CohereInference, model, apiKey);
6826
+ const cohere = (fn, model, apiKey) => {
6827
+ const wrappedFn = async (args) => {
6828
+ return await ContextService.runInContext(async () => {
6829
+ return await fn(...args);
6830
+ }, {
6831
+ apiKey,
6832
+ inference: InferenceName$1.CohereInference,
6833
+ model,
6834
+ });
6835
+ };
6836
+ return wrappedFn;
7083
6837
  };
7084
6838
  /**
7085
- * Generate structured trading signal from Alibaba Cloud Qwen models.
6839
+ * Wrap async function with Alibaba Qwen inference context.
7086
6840
  *
7087
- * Uses Alibaba DashScope API through direct HTTP requests. Does NOT support token rotation.
6841
+ * Creates a higher-order function that executes the provided async function
6842
+ * within an Alibaba DashScope API inference context.
7088
6843
  *
7089
- * @param messages - Array of outline messages (user/assistant/system)
6844
+ * @template T - Async function type
6845
+ * @param fn - Async function to wrap
7090
6846
  * @param model - Qwen model name (e.g., "qwen-max")
7091
- * @param apiKey - Single API key (token rotation not supported)
7092
- * @returns Promise resolving to structured trading signal
7093
- * @throws Error if apiKey is an array (token rotation not supported)
6847
+ * @param apiKey - Single API key or array of keys
6848
+ * @returns Wrapped function with same signature as input
7094
6849
  *
7095
6850
  * @example
7096
6851
  * ```typescript
7097
6852
  * import { alibaba } from '@backtest-kit/ollama';
7098
6853
  *
7099
- * const signal = await alibaba(messages, 'qwen-max', process.env.ALIBABA_API_KEY);
6854
+ * const wrappedFn = alibaba(myAsyncFn, 'qwen-max', process.env.ALIBABA_API_KEY);
6855
+ * const result = await wrappedFn(args);
7100
6856
  * ```
7101
6857
  */
7102
- const alibaba = async (messages, model, apiKey) => {
7103
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.AlibabaInference, model, apiKey);
6858
+ const alibaba = (fn, model, apiKey) => {
6859
+ const wrappedFn = async (args) => {
6860
+ return await ContextService.runInContext(async () => {
6861
+ return await fn(...args);
6862
+ }, {
6863
+ apiKey,
6864
+ inference: InferenceName$1.AlibabaInference,
6865
+ model,
6866
+ });
6867
+ };
6868
+ return wrappedFn;
7104
6869
  };
7105
6870
  /**
7106
- * Generate structured trading signal from Zhipu AI GLM-4 models.
6871
+ * Wrap async function with Zhipu AI GLM-4 inference context.
7107
6872
  *
7108
- * Uses Zhipu AI's GLM-4 through OpenAI-compatible Z.ai API. Does NOT support token rotation.
7109
- * GLM-4 is a powerful Chinese language model with strong reasoning capabilities.
6873
+ * Creates a higher-order function that executes the provided async function
6874
+ * within a Zhipu AI GLM-4 inference context via OpenAI-compatible Z.ai API.
7110
6875
  *
7111
- * @param messages - Array of outline messages (user/assistant/system)
6876
+ * @template T - Async function type
6877
+ * @param fn - Async function to wrap
7112
6878
  * @param model - GLM-4 model name (e.g., "glm-4-plus", "glm-4-air")
7113
- * @param apiKey - Single API key (token rotation not supported)
7114
- * @returns Promise resolving to structured trading signal
7115
- * @throws Error if apiKey is an array (token rotation not supported)
6879
+ * @param apiKey - Single API key or array of keys
6880
+ * @returns Wrapped function with same signature as input
7116
6881
  *
7117
6882
  * @example
7118
6883
  * ```typescript
7119
6884
  * import { glm4 } from '@backtest-kit/ollama';
7120
6885
  *
7121
- * const signal = await glm4(messages, 'glm-4-plus', process.env.ZAI_API_KEY);
7122
- * console.log(`Position: ${signal.position}`);
7123
- * console.log(`Entry: ${signal.priceOpen}`);
6886
+ * const wrappedFn = glm4(myAsyncFn, 'glm-4-plus', process.env.ZAI_API_KEY);
6887
+ * const result = await wrappedFn(args);
7124
6888
  * ```
7125
6889
  */
7126
- const glm4 = async (messages, model, apiKey) => {
7127
- return await engine$1.outlinePublicService.getCompletion(messages, InferenceName$1.GLM4Inference, model, apiKey);
6890
+ const glm4 = (fn, model, apiKey) => {
6891
+ const wrappedFn = async (args) => {
6892
+ return await ContextService.runInContext(async () => {
6893
+ return await fn(...args);
6894
+ }, {
6895
+ apiKey,
6896
+ inference: InferenceName$1.GLM4Inference,
6897
+ model,
6898
+ });
6899
+ };
6900
+ return wrappedFn;
7128
6901
  };
7129
6902
 
7130
6903
  /**
@@ -7148,64 +6921,6 @@ const setLogger = (logger) => {
7148
6921
  engine$1.loggerService.setLogger(logger);
7149
6922
  };
7150
6923
 
7151
- /**
7152
- * Overrides the default signal format schema for LLM-generated trading signals.
7153
- *
7154
- * This function allows customization of the structured output format used by the
7155
- * SignalOutline. It replaces the default signal schema with a custom Zod schema,
7156
- * enabling flexible signal structure definitions while maintaining type safety.
7157
- *
7158
- * The override affects all subsequent signal generation calls using SignalOutline
7159
- * until the application restarts or the schema is overridden again.
7160
- *
7161
- * @template ZodInput - The Zod schema type used for validation and type inference
7162
- *
7163
- * @param {ZodInput} format - Custom Zod schema defining the signal structure.
7164
- * Must be a valid Zod type (z.object, z.string, etc.)
7165
- *
7166
- * @example
7167
- * ```typescript
7168
- * import { z } from 'zod';
7169
- * import { overrideSignalFormat } from '@backtest-kit/ollama';
7170
- *
7171
- * // Override with custom signal schema
7172
- * const CustomSignalSchema = z.object({
7173
- * position: z.enum(['long', 'short', 'wait']),
7174
- * price_open: z.number(),
7175
- * confidence: z.number().min(0).max(100),
7176
- * custom_field: z.string()
7177
- * });
7178
- *
7179
- * overrideSignalFormat(CustomSignalSchema);
7180
- * ```
7181
- *
7182
- * @example
7183
- * ```typescript
7184
- * // Override with simplified schema
7185
- * const SimpleSignalSchema = z.object({
7186
- * action: z.enum(['buy', 'sell', 'hold']),
7187
- * price: z.number()
7188
- * });
7189
- *
7190
- * overrideSignalFormat(SimpleSignalSchema);
7191
- * ```
7192
- *
7193
- * @remarks
7194
- * - The custom schema replaces the default SignalSchema completely
7195
- * - Schema name in OpenAI format is always "position_open_decision"
7196
- * - Changes persist until application restart or next override
7197
- * - Ensure the custom schema matches your signal processing logic
7198
- *
7199
- * @see {@link SignalSchema} - Default signal schema structure
7200
- * @see {@link OutlineName.SignalOutline} - Outline being overridden
7201
- */
7202
- function overrideSignalFormat(format) {
7203
- overrideOutline({
7204
- outlineName: OutlineName$1.SignalOutline,
7205
- format: zodResponseFormat(format, "position_open_decision"),
7206
- });
7207
- }
7208
-
7209
6924
  const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
7210
6925
  /**
7211
6926
  * Dumps signal data and LLM conversation history to markdown files.
@@ -7373,7 +7088,36 @@ async function validate(args = {}) {
7373
7088
  return await validateInternal(args);
7374
7089
  }
7375
7090
 
7091
+ const MODULE_TYPE_SYMBOL = Symbol("module-type");
7092
+ class Module {
7093
+ constructor(path, baseDir) {
7094
+ this.path = path;
7095
+ this.baseDir = baseDir;
7096
+ this.__type__ = MODULE_TYPE_SYMBOL;
7097
+ }
7098
+ }
7099
+ Module.fromPath = (path, baseDir = join(process.cwd(), "config/prompt")) => {
7100
+ if (!path || typeof path !== "string" || !path.trim()) {
7101
+ throw new Error("Path must be a non-empty string");
7102
+ }
7103
+ return new Module(path, baseDir);
7104
+ };
7105
+ Module.isModule = (value) => {
7106
+ return (value !== null &&
7107
+ typeof value === "object" &&
7108
+ value.__type__ === MODULE_TYPE_SYMBOL);
7109
+ };
7110
+
7376
7111
  const METHOD_NAME_SIGNAL = "history.commitSignalPromptHistory";
7112
+ const GET_PROMPT_FN = async (source) => {
7113
+ if (Prompt.isPrompt(source)) {
7114
+ return source;
7115
+ }
7116
+ if (Module.isModule(source)) {
7117
+ return await engine$1.promptCacheService.readModule(source);
7118
+ }
7119
+ throw new Error("Source must be a Prompt or Module instance");
7120
+ };
7377
7121
  /**
7378
7122
  * Commits signal prompt history to the message array.
7379
7123
  *
@@ -7382,40 +7126,40 @@ const METHOD_NAME_SIGNAL = "history.commitSignalPromptHistory";
7382
7126
  * at the end of the history array if they are not empty.
7383
7127
  *
7384
7128
  * Context extraction:
7385
- * - symbol: Provided as parameter for debugging convenience
7386
- * - backtest mode: From ExecutionContext
7387
- * - strategyName, exchangeName, frameName: From MethodContext
7129
+ * - symbol: From getSymbol()
7130
+ * - backtest mode: From getMode()
7131
+ * - strategyName, exchangeName, frameName: From getContext()
7388
7132
  *
7389
- * @param symbol - Trading symbol (e.g., "BTCUSDT") for debugging convenience
7133
+ * @param source - Module object containing path to .cjs module
7390
7134
  * @param history - Message array to append prompts to
7391
7135
  * @returns Promise that resolves when prompts are added
7392
7136
  * @throws Error if ExecutionContext or MethodContext is not active
7393
7137
  *
7394
- * @example
7395
- * ```typescript
7396
- * const messages: MessageModel[] = [];
7397
- * await commitSignalPromptHistory("BTCUSDT", messages);
7398
- * // messages now contains system prompts at start and user prompt at end
7399
7138
  * ```
7400
7139
  */
7401
- async function commitSignalPromptHistory(symbol, history) {
7140
+ async function commitPrompt(source, history) {
7402
7141
  engine$1.loggerService.log(METHOD_NAME_SIGNAL, {
7403
- symbol,
7142
+ source,
7404
7143
  });
7144
+ const symbol = await getSymbol();
7405
7145
  const { strategyName, exchangeName, frameName } = await getContext();
7406
7146
  const mode = await getMode();
7407
7147
  const isBacktest = mode === "backtest";
7408
- const systemPrompts = await engine$1.signalPromptService.getSystemPrompt(symbol, strategyName, exchangeName, frameName, isBacktest);
7409
- const userPrompt = await engine$1.signalPromptService.getUserPrompt(symbol, strategyName, exchangeName, frameName, isBacktest);
7148
+ const prompt = await GET_PROMPT_FN(source);
7149
+ const systemPrompts = await engine$1.resolvePromptService.getSystemPrompt(prompt, symbol, strategyName, exchangeName, frameName, isBacktest);
7150
+ const userPrompt = await engine$1.resolvePromptService.getUserPrompt(prompt, symbol, strategyName, exchangeName, frameName, isBacktest);
7410
7151
  if (systemPrompts.length > 0) {
7411
7152
  for (const content of systemPrompts) {
7153
+ if (!content.trim()) {
7154
+ continue;
7155
+ }
7412
7156
  history.unshift({
7413
7157
  role: "system",
7414
7158
  content,
7415
7159
  });
7416
7160
  }
7417
7161
  }
7418
- if (userPrompt && userPrompt.trim() !== "") {
7162
+ if (userPrompt && userPrompt.trim()) {
7419
7163
  history.push({
7420
7164
  role: "user",
7421
7165
  content: userPrompt,
@@ -7722,4 +7466,4 @@ class OptimizerUtils {
7722
7466
  */
7723
7467
  const Optimizer = new OptimizerUtils();
7724
7468
 
7725
- export { Optimizer, addOptimizerSchema, alibaba, claude, cohere, commitSignalPromptHistory, deepseek, dumpSignalData, getOptimizerSchema, glm4, gpt5, grok, hf, engine as lib, listOptimizerSchema, listenError, listenOptimizerProgress, mistral, ollama, overrideSignalFormat, perplexity, setLogger, validate };
7469
+ export { CompletionName, Module, Optimizer, Prompt, addOptimizerSchema, alibaba, claude, cohere, commitPrompt, deepseek, dumpSignalData, getOptimizerSchema, glm4, gpt5, grok, hf, engine as lib, listOptimizerSchema, listenError, listenOptimizerProgress, mistral, ollama, perplexity, setLogger, validate };