@bitget-ai/getagent-skill 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/VERSION +1 -1
  4. package/package.json +1 -1
  5. package/skills/getagent/SKILL.md +29 -1
  6. package/skills/getagent/examples/btc-ema-cross-demo/backtest.yaml +4 -0
  7. package/skills/getagent/references/api/publish.md +4 -0
  8. package/skills/getagent/references/backtest-engine.md +80 -151
  9. package/skills/getagent/references/package-schema.md +30 -8
  10. package/skills/getagent/references/sdk/backtest/catalog.md +15 -2
  11. package/skills/getagent/references/sdk/data/arxiv.md +8 -10
  12. package/skills/getagent/references/sdk/data/catalog.md +1 -4
  13. package/skills/getagent/references/sdk/data/commodity.md +25 -39
  14. package/skills/getagent/references/sdk/data/coverage.md +0 -3
  15. package/skills/getagent/references/sdk/data/crypto.md +158 -357
  16. package/skills/getagent/references/sdk/data/currency.md +7 -15
  17. package/skills/getagent/references/sdk/data/derivatives.md +35 -50
  18. package/skills/getagent/references/sdk/data/economy.md +175 -259
  19. package/skills/getagent/references/sdk/data/equity.md +257 -393
  20. package/skills/getagent/references/sdk/data/etf.md +40 -64
  21. package/skills/getagent/references/sdk/data/famafrench.md +38 -50
  22. package/skills/getagent/references/sdk/data/fixedincome.md +89 -139
  23. package/skills/getagent/references/sdk/data/imf_utils.md +0 -8
  24. package/skills/getagent/references/sdk/data/index.md +18 -32
  25. package/skills/getagent/references/sdk/data/news.md +52 -58
  26. package/skills/getagent/references/sdk/data/playbook-supported.md +965 -1600
  27. package/skills/getagent/references/sdk/data/regulators.md +23 -43
  28. package/skills/getagent/references/sdk/data/sentiment.md +12 -34
  29. package/skills/getagent/references/sdk/data/uscongress.md +13 -21
  30. package/skills/getagent/references/sdk/data/web_search.md +3 -7
  31. package/skills/getagent/references/sdk/data/wikipedia.md +12 -18
  32. package/skills/getagent/scripts/validate.py +127 -1
@@ -14,7 +14,7 @@ are callable through the DataSDK wrapper.
14
14
  ### `wikipedia.content`
15
15
 
16
16
  ```python
17
- data.wikipedia.content(title=None, lang='en', intro_only=False, provider='wikipedia')
17
+ data.wikipedia.content(title=None, lang='en', intro_only=False)
18
18
  ```
19
19
 
20
20
  Summary: Content
@@ -24,7 +24,6 @@ Summary: Content
24
24
  | Endpoint ID | `wikipedia.content` |
25
25
  | HTTP | `GET` |
26
26
  | Path | `/inner/v1/agent-data/wikipedia/content` |
27
- | Default provider | `wikipedia` |
28
27
  | SDK | `supported` |
29
28
  | Host | `supported` |
30
29
  | Notes | - |
@@ -33,17 +32,16 @@ Summary: Content
33
32
 
34
33
  | Param | Required | Type | Default | Notes |
35
34
  |---|---|---|---|---|
36
- | `title` | `no` | `string | null` | `-` | Wikipedia article title to retrieve. (provider: wikipedia) |
37
- | `lang` | `no` | `string` | `en` | Wikipedia language edition. (provider: wikipedia) |
38
- | `intro_only` | `no` | `boolean` | `false` | Return only the introductory section instead of the full article. (provider: wikipedia) |
39
- | `provider` | `no` | `string` | `wikipedia` | - |
35
+ | `title` | `no` | `string | null` | `-` | Wikipedia article title to retrieve. |
36
+ | `lang` | `no` | `string` | `en` | Wikipedia language edition. |
37
+ | `intro_only` | `no` | `boolean` | `false` | Return only the introductory section instead of the full article. |
40
38
 
41
39
  ---
42
40
 
43
41
  ### `wikipedia.search`
44
42
 
45
43
  ```python
46
- data.wikipedia.search(query=None, max_results=10, page=1, lang='en', provider='wikipedia')
44
+ data.wikipedia.search(query=None, max_results=10, page=1, lang='en')
47
45
  ```
48
46
 
49
47
  Summary: Search
@@ -53,7 +51,6 @@ Summary: Search
53
51
  | Endpoint ID | `wikipedia.search` |
54
52
  | HTTP | `GET` |
55
53
  | Path | `/inner/v1/agent-data/wikipedia/search` |
56
- | Default provider | `wikipedia` |
57
54
  | SDK | `supported` |
58
55
  | Host | `supported` |
59
56
  | Notes | - |
@@ -62,18 +59,17 @@ Summary: Search
62
59
 
63
60
  | Param | Required | Type | Default | Notes |
64
61
  |---|---|---|---|---|
65
- | `query` | `no` | `string | null` | `-` | Search query string. (provider: wikipedia) |
66
- | `max_results` | `no` | `integer` | `10` | Maximum number of results. (provider: wikipedia) |
67
- | `page` | `no` | `integer` | `1` | Page number, starting at 1. (provider: wikipedia) |
68
- | `lang` | `no` | `string` | `en` | Wikipedia language edition. (provider: wikipedia) |
69
- | `provider` | `no` | `string` | `wikipedia` | - |
62
+ | `query` | `no` | `string | null` | `-` | Search query string. |
63
+ | `max_results` | `no` | `integer` | `10` | Maximum number of results. |
64
+ | `page` | `no` | `integer` | `1` | Page number, starting at 1. |
65
+ | `lang` | `no` | `string` | `en` | Wikipedia language edition. |
70
66
 
71
67
  ---
72
68
 
73
69
  ### `wikipedia.summary`
74
70
 
75
71
  ```python
76
- data.wikipedia.summary(title=None, lang='en', provider='wikipedia')
72
+ data.wikipedia.summary(title=None, lang='en')
77
73
  ```
78
74
 
79
75
  Summary: Summary
@@ -83,7 +79,6 @@ Summary: Summary
83
79
  | Endpoint ID | `wikipedia.summary` |
84
80
  | HTTP | `GET` |
85
81
  | Path | `/inner/v1/agent-data/wikipedia/summary` |
86
- | Default provider | `wikipedia` |
87
82
  | SDK | `supported` |
88
83
  | Host | `supported` |
89
84
  | Notes | - |
@@ -92,6 +87,5 @@ Summary: Summary
92
87
 
93
88
  | Param | Required | Type | Default | Notes |
94
89
  |---|---|---|---|---|
95
- | `title` | `no` | `string | null` | `-` | Wikipedia article title to retrieve. (provider: wikipedia) |
96
- | `lang` | `no` | `string` | `en` | Wikipedia language edition. (provider: wikipedia) |
97
- | `provider` | `no` | `string` | `wikipedia` | - |
90
+ | `title` | `no` | `string | null` | `-` | Wikipedia article title to retrieve. |
91
+ | `lang` | `no` | `string` | `en` | Wikipedia language edition. |
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
  import ast
20
20
  import re
21
21
  import sys
22
+ from datetime import datetime
22
23
  from pathlib import Path, PurePosixPath
23
24
  from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
24
25
 
@@ -52,6 +53,7 @@ MANIFEST_REQUIRED_FIELDS = [
52
53
 
53
54
  NAME_PATTERN = re.compile(r"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$")
54
55
  BACKTEST_BAR_FIELD_PATTERN = re.compile(r"^[a-z][a-z0-9_]*$")
56
+ PUBLIC_SYMBOL_TOKEN_PATTERN = re.compile(r"\b[A-Z0-9]{2,20}(?:USDT|USDC|USD|BTC|ETH)\b")
55
57
  CRON_EVERY_MINUTES_PATTERN = re.compile(r"^\*/(\d+)$")
56
58
  MIN_SCHEDULE_INTERVAL_MINUTES = 10
57
59
  DEFAULT_SCHEDULE_TZ = "Asia/Shanghai"
@@ -375,6 +377,21 @@ def validate_manifest(pkg_dir: Path, result: ValidationResult) -> dict:
375
377
  symbols = data.get("trading_symbols", [])
376
378
  if not isinstance(symbols, list) or not all(isinstance(item, str) and item.strip() for item in symbols):
377
379
  result.error("manifest.yaml: 'trading_symbols' must be a non-empty list of strings")
380
+ else:
381
+ normalized_symbols = {str(item).strip().upper() for item in symbols}
382
+ for field in ("display_name", "description"):
383
+ text = str(data.get(field, "") or "")
384
+ unknown_symbols = sorted(
385
+ token
386
+ for token in PUBLIC_SYMBOL_TOKEN_PATTERN.findall(text.upper())
387
+ if token not in normalized_symbols
388
+ )
389
+ if unknown_symbols:
390
+ result.error(
391
+ f"manifest.yaml: '{field}' mentions symbols {unknown_symbols} "
392
+ f"outside trading_symbols {sorted(normalized_symbols)}; "
393
+ "if you corrected a typo or changed the fallback symbol, update all display text"
394
+ )
378
395
 
379
396
  decision_mode = data.get("decision_mode", "")
380
397
  if decision_mode and decision_mode not in DECISION_MODES:
@@ -465,6 +482,24 @@ def validate_backtest_yaml(pkg_dir: Path, manifest: dict, result: ValidationResu
465
482
  if not str(payload.get(field, "") or "").strip():
466
483
  result.error(f"{prefix}: missing '{field}'")
467
484
 
485
+ def _parse_backtest_datetime(value: object) -> datetime | None:
486
+ text = str(value or "").strip()
487
+ if not text:
488
+ return None
489
+ try:
490
+ return datetime.fromisoformat(text.replace("Z", "+00:00"))
491
+ except ValueError:
492
+ return None
493
+
494
+ def _instrument_symbol(item: dict) -> str:
495
+ raw = str(
496
+ item.get("raw_symbol")
497
+ or item.get("symbol")
498
+ or str(item.get("id", "") or "").split(".", 1)[0]
499
+ or ""
500
+ ).strip().upper()
501
+ return raw
502
+
468
503
  def _validate_instrument(item: dict, *, prefix: str) -> None:
469
504
  kind = str(item.get("kind", "") or "").strip().lower()
470
505
  if kind not in BACKTEST_INSTRUMENT_KINDS:
@@ -513,6 +548,24 @@ def validate_backtest_yaml(pkg_dir: Path, manifest: dict, result: ValidationResu
513
548
  continue
514
549
  seen.add(field)
515
550
 
551
+ def _validate_no_provider(payload: object, *, prefix: str) -> None:
552
+ if isinstance(payload, dict):
553
+ if "provider" in payload:
554
+ result.error(
555
+ f"{prefix}: 'provider' is not allowed; GetAgent routes historical "
556
+ "data through the managed DataSDK provider"
557
+ )
558
+ for key, value in payload.items():
559
+ _validate_no_provider(value, prefix=f"{prefix}.{key}")
560
+ elif isinstance(payload, list):
561
+ for index, value in enumerate(payload):
562
+ _validate_no_provider(value, prefix=f"{prefix}[{index}]")
563
+
564
+ def _record_backtest_symbol(item: dict, *, prefix: str) -> None:
565
+ symbol = _instrument_symbol(item)
566
+ if symbol:
567
+ backtest_symbols.append((prefix, symbol))
568
+
516
569
  path = pkg_dir / "backtest.yaml"
517
570
  if not path.exists():
518
571
  return
@@ -522,6 +575,9 @@ def validate_backtest_yaml(pkg_dir: Path, manifest: dict, result: ValidationResu
522
575
  result.error("backtest.yaml: invalid YAML syntax")
523
576
  return
524
577
 
578
+ _validate_no_provider(data, prefix="backtest.yaml")
579
+ backtest_symbols: list[tuple[str, str]] = []
580
+
525
581
  if manifest.get("backtest_support") != "full":
526
582
  result.error("backtest.yaml is only allowed when manifest.yaml sets backtest_support: full")
527
583
 
@@ -572,6 +628,7 @@ def validate_backtest_yaml(pkg_dir: Path, manifest: dict, result: ValidationResu
572
628
  result.error("backtest.yaml: use either 'instrument' or 'instruments', not both")
573
629
  elif has_single:
574
630
  _validate_instrument(data["instrument"], prefix="backtest.yaml.instrument")
631
+ _record_backtest_symbol(data["instrument"], prefix="backtest.yaml.instrument")
575
632
  elif has_many:
576
633
  instruments = data.get("instruments") or []
577
634
  if not instruments:
@@ -581,18 +638,68 @@ def validate_backtest_yaml(pkg_dir: Path, manifest: dict, result: ValidationResu
581
638
  result.error(f"backtest.yaml.instruments[{index}] must be a mapping")
582
639
  continue
583
640
  _validate_instrument(item, prefix=f"backtest.yaml.instruments[{index}]")
641
+ _record_backtest_symbol(item, prefix=f"backtest.yaml.instruments[{index}]")
584
642
  else:
585
643
  result.error("backtest.yaml: missing 'instrument' or 'instruments'")
586
644
 
645
+ manifest_symbols = {
646
+ str(item or "").strip().upper()
647
+ for item in manifest.get("trading_symbols", [])
648
+ if str(item or "").strip()
649
+ }
650
+ if manifest_symbols and backtest_symbols:
651
+ actual_symbols = {symbol for _, symbol in backtest_symbols}
652
+ extra_symbols = [
653
+ f"{prefix}={symbol}"
654
+ for prefix, symbol in backtest_symbols
655
+ if symbol not in manifest_symbols
656
+ ]
657
+ missing_symbols = sorted(manifest_symbols - actual_symbols)
658
+ if extra_symbols or missing_symbols:
659
+ result.error(
660
+ "backtest.yaml: instruments must match manifest.yaml trading_symbols; "
661
+ f"unexpected backtest symbols={extra_symbols or []}, "
662
+ f"missing manifest symbols={missing_symbols or []}"
663
+ )
664
+
587
665
  execution = data.get("execution")
588
- if execution is not None and not isinstance(execution, dict):
666
+ if not isinstance(execution, dict):
589
667
  result.error("backtest.yaml: 'execution' must be a mapping")
668
+ else:
669
+ start = str(execution.get("start") or "").strip()
670
+ end = str(execution.get("end") or "").strip()
671
+ if not start or not end:
672
+ result.error(
673
+ "backtest.yaml.execution: 'start' and 'end' are required so "
674
+ "the sandbox can prove the replay window has bars"
675
+ )
676
+ else:
677
+ parsed_start = _parse_backtest_datetime(start)
678
+ parsed_end = _parse_backtest_datetime(end)
679
+ if parsed_start is None or parsed_end is None:
680
+ result.error("backtest.yaml.execution: 'start' and 'end' must be ISO datetimes")
681
+ elif parsed_end <= parsed_start:
682
+ result.error("backtest.yaml.execution: 'end' must be after 'start'")
590
683
 
591
684
  data_requirements = data.get("data_requirements")
592
685
  if data_requirements is not None and not isinstance(data_requirements, dict):
593
686
  result.error("backtest.yaml: 'data_requirements' must be a mapping")
594
687
  elif isinstance(data_requirements, dict):
595
688
  _validate_required_bar_fields(data_requirements.get("required_bar_fields"))
689
+ required_fields = data_requirements.get("required_bar_fields")
690
+ if isinstance(required_fields, list):
691
+ source_text = "\n".join(
692
+ path.read_text(encoding="utf-8", errors="ignore")
693
+ for path in (pkg_dir / "src").rglob("*.py")
694
+ )
695
+ for field in required_fields:
696
+ if isinstance(field, str) and field.strip() and field.strip() not in source_text:
697
+ result.error(
698
+ "backtest.yaml.data_requirements.required_bar_fields: "
699
+ f"declares '{field.strip()}' but src/** never references it; "
700
+ "build the feature column with backtest.build_feature_frame(...) "
701
+ "or remove the declaration"
702
+ )
596
703
 
597
704
 
598
705
  def _local_import_roots(pkg_dir: Path) -> set[str]:
@@ -764,6 +871,23 @@ def _check_contract_tpsl_helper_call(tree: ast.AST, *, source_path: str) -> list
764
871
  return errors
765
872
 
766
873
 
874
+ def _check_data_provider_keyword(tree: ast.AST, *, source_path: str) -> list[str]:
875
+ errors: list[str] = []
876
+ for node in ast.walk(tree):
877
+ if not isinstance(node, ast.Call):
878
+ continue
879
+ path = _attribute_path(node.func)
880
+ if not path or "data" not in path:
881
+ continue
882
+ for keyword in node.keywords:
883
+ if keyword.arg == "provider":
884
+ errors.append(
885
+ f"{source_path}: do not pass provider=... to getagent.data calls "
886
+ f"(line {keyword.value.lineno}); the managed DataSDK provider is selected by the platform"
887
+ )
888
+ return errors
889
+
890
+
767
891
  def _test_contains_follow_trade_guard(test: ast.AST) -> bool:
768
892
  if isinstance(test, ast.Call):
769
893
  return _attribute_path(test)[-2:] == ["runtime", "is_follow_trade"]
@@ -865,6 +989,8 @@ def validate_src_tree(pkg_dir: Path, result: ValidationResult) -> None:
865
989
  result.error(error)
866
990
  for error in _check_contract_tpsl_helper_call(tree, source_path=rel_path):
867
991
  result.error(error)
992
+ for error in _check_data_provider_keyword(tree, source_path=rel_path):
993
+ result.error(error)
868
994
  for error in _check_live_trade_mutation_guards(tree, source_path=rel_path):
869
995
  result.error(error)
870
996