When a developer types "Show me NVIDIA's after-hours price" into an AI coding assistant, most systems draw a blank. The assistant recognizes the intent — but it has no contract specifying what parameters to send, what the response looks like, or how to handle rate limits. It cannot invoke the right API because it has never been taught the vocabulary.
The TickDB SKILL protocol exists to solve this exact problem. At its core is a file called skill.md — a machine-readable specification that teaches any compatible AI assistant how to interrogate TickDB's market data endpoints. This article dissects that specification end-to-end: what it contains, how AI parsers interpret it, and how the Function Calling schema connects your natural-language query to a live API call.
For quant developers building AI-augmented trading workflows, the SKILL protocol is the bridge between conversational intent and programmatic data retrieval. Understanding its architecture lets you build AI tools that do not hallucinate endpoint names or parameter structures — tools that reliably fetch the data you need, when you need it.
The Pain Point: Why AI Assistants Fail at Market Data Queries
The fundamental problem is a vocabulary gap. Generic LLMs are trained on natural language and open-source code. They encounter market data APIs infrequently — and when they do, the encounters are scattered across blog posts, outdated documentation, and sample code that never explains the full contract.
Consider what happens when you ask a standard AI assistant to fetch real-time depth data:
- The model may guess an endpoint like
/api/v1/depthwithout verifying it exists. - It may omit authentication headers, assuming no API key is required.
- It may ignore rate-limit handling, assuming every request succeeds.
- It may format the response parameters incorrectly, expecting snake_case where TickDB returns camelCase.
The result is a plausible-sounding but non-functional code snippet. The AI does not know what TickDB's endpoints look like because it was never given a definitive reference.
skill.md closes this gap by providing a complete, structured description of every endpoint, parameter, response shape, and behavioral edge case. When an AI assistant reads this file, it gains the same precision a human developer gets from reading the official documentation — but in a format designed for machine ingestion rather than human browsing.
The SKILL Protocol Architecture
The SKILL protocol is not a proprietary LLM format. It is a thin layer on top of existing standards — JSON Schema for data types, OpenAPI-inspired structure for endpoints, and a conventions layer for multi-turn conversation state.
Core Components
The protocol has three layers:
| Layer | Purpose | Format |
|---|---|---|
| Metadata block | Describes the skill name, version, supported intents, and global behavior | YAML front matter |
| Endpoint definitions | Each API call is described with parameters, return types, and error codes | JSON Schema fragments |
| Conventions | Guides the AI on how to handle multi-turn dialogue, parameter deduction, and confirmation flows | Prose + structured directives |
The file is served at a fixed URL — http://skill.md/ — and cached by the AI assistant on first load. Subsequent queries consult the local cache unless a version check is explicitly triggered.
Metadata Block
The metadata block appears at the top of skill.md as YAML front matter. It is human-readable and machine-parseable. Here is a representative structure:
---
name: tickdb-market-data
version: "2.1"
description: Real-time and historical market data for equities, crypto, forex, and commodities
base_url: https://api.tickdb.ai/v1
auth_method: header
auth_parameter: X-API-Key
rate_limit:
requests_per_minute: 60
burst: 10
error_code: 3001
retry_header: Retry-After
intents:
- name: get_realtime_depth
description: Retrieve order book depth for a given symbol
trigger_phrases:
- "order book"
- "bid ask spread"
- "depth of market"
- name: get_kline_historical
description: Fetch OHLCV candles for backtesting
trigger_phrases:
- "historical price"
- "candlestick"
- "kline data"
- name: get_symbol_info
description: List available trading symbols
trigger_phrases:
- "available symbols"
- "what markets"
- "list tickers"
---
The intents array is the most critical section for AI parsing. Each intent maps a user goal to an endpoint, and the trigger_phrases field gives the AI a lexical map for recognizing when this intent should be activated. This is not regex matching — it is a soft signal that helps the model prioritize among multiple possible interpretations.
Endpoint Definitions: The Function Calling Schema
Each endpoint in skill.md is defined as a Function Calling schema — the same JSON structure that OpenAI, Anthropic, and most enterprise LLM providers use to describe tool interfaces. This is deliberate. By using a standard format, the SKILL protocol is compatible with the Function Calling capabilities of any modern LLM without requiring custom fine-tuning.
Full Endpoint Definition Example
Here is the complete definition for the depth endpoint, including all required and optional parameters:
{
"type": "function",
"function": {
"name": "tickdb_get_depth",
"description": "Retrieve real-time order book depth for a trading symbol. Returns bid and ask levels up to 50 levels for supported markets. Use this when the user asks about the order book, bid-ask spread, or buy/sell pressure.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "Trading symbol in exchange format. Format: TICKER.EXCHANGE (e.g., AAPL.US, BTC.BT, 9999.HK). For crypto, use BASEQUOTE.EX (e.g., BTCUSDT.BT).",
"examples": ["AAPL.US", "BTCUSDT.BT", "9999.HK", "EURUSD.FX"]
},
"limit": {
"type": "integer",
"description": "Number of price levels to return per side. Range: 1-50. Default varies by market: US equities default to 1, crypto and HK default to 10.",
"minimum": 1,
"maximum": 50,
"default": 10
},
"depth_channel": {
"type": "string",
"enum": ["depth", "depth_snapshoot", "tbi"],
"description": "Channel type for depth data. 'depth' is standard real-time stream. 'depth_snapshoot' returns a one-time snapshot. 'tbi' returns order book imbalance metrics.",
"default": "depth"
}
},
"required": ["symbol"],
"additionalProperties": false
},
"response": {
"type": "object",
"properties": {
"code": { "type": "integer", "description": "0 on success; see error code reference" },
"message": { "type": "string", "description": "Human-readable status message" },
"data": {
"type": "object",
"properties": {
"symbol": { "type": "string" },
"timestamp": { "type": "integer", "description": "Unix timestamp in milliseconds" },
"bids": {
"type": "array",
"description": "Array of [price, size] pairs, sorted descending by price",
"items": { "type": "array", "items": { "type": "number" }, "maxItems": 2 }
},
"asks": {
"type": "array",
"description": "Array of [price, size] pairs, sorted ascending by price",
"items": { "type": "array", "items": { "type": "number" }, "maxItems": 2 }
}
}
}
}
},
"errors": {
"1001": "Invalid or missing API key",
"1002": "API key inactive or revoked",
"2002": "Symbol not found — verify via /v1/symbols/available",
"3001": "Rate limit exceeded — check Retry-After header"
}
}
}
This schema tells the AI three things it cannot infer from natural language alone:
- What the function is named (
tickdb_get_depth) — the AI uses this in its Function Calling request. - What parameters are required — only
symbolis mandatory;limitanddepth_channelare optional. - What the response looks like — the AI can now extract bid/ask arrays and compute derived metrics like the pressure ratio without hallucinating a response format.
Error Code Mapping
The errors block maps numeric codes to human-readable explanations. This is used by the AI to generate meaningful error messages when a call fails:
{
"errors": {
"1001": "Invalid or missing API key. Check that TICKDB_API_KEY is set in your environment variables.",
"1002": "API key inactive. Verify your key in the TickDB dashboard.",
"2002": "Symbol not found. Use the /v1/symbols/available endpoint to list valid symbols before requesting data.",
"3001": "Rate limit exceeded. Wait for the duration specified in the Retry-After header before retrying."
}
}
When the AI encounters a 3001 error during a live query, it reads this mapping and responds with: "You've hit the rate limit (code 3001). I'll wait 5 seconds and retry." instead of forwarding an opaque error code to the user.
How the AI Parses and Invokes the Protocol
The parsing process follows a predictable sequence:
User query → Intent classification → Parameter extraction → Function call construction → API request → Response parsing → Natural language reply
Step 1: Intent Classification
The AI receives the user's query and consults the intents array in the metadata block. Each intent has trigger_phrases that the model uses for soft matching. The model does not do string matching — it uses semantic similarity to score each intent against the query.
For example, the query "What's the bid-ask spread on AAPL right now?" triggers get_realtime_depth because "bid-ask spread" and "right now" both map to depth data retrieval.
If multiple intents score similarly, the AI asks a clarifying question before proceeding. This is a deliberate guardrail — the SKILL protocol includes a conventions.confirm_ambiguous_params directive that tells the AI to pause and confirm when it cannot confidently resolve a symbol or parameter.
Step 2: Parameter Extraction
Once an intent is selected, the AI extracts parameters from the query using the endpoint definition. The symbol parameter is extracted from the natural language:
- "AAPL" →
AAPL.US(AI must know the exchange suffix from conventions) - "Bitcoin versus USDT" →
BTCUSDT.BT - "Hang Seng Index" →
HSI.HK(AI must map the common name to the correct ticker)
The conventions block includes a symbol_aliases section that maps common names to their canonical tickers:
conventions:
symbol_aliases:
"Hang Seng Index": "HSI.HK"
"Nikkei 225": "NKY.JP"
"Bitcoin": "BTCUSDT.BT"
"Ethereum": "ETHUSDT.BT"
"NVIDIA": "NVDA.US"
"Apple": "AAPL.US"
When the AI encounters "Hang Seng Index" in a query, it uses this mapping to resolve the correct symbol before constructing the API call.
Step 3: Function Call Construction
The AI builds a JSON payload matching the Function Calling schema. For the depth query above:
{
"name": "tickdb_get_depth",
"arguments": {
"symbol": "AAPL.US",
"limit": 10,
"depth_channel": "depth"
}
}
This payload is sent to the TickDB API. The AI does not write raw fetch() or requests.get() calls — it delegates execution to the Function Calling runtime, which handles the actual HTTP request. The AI's role is purely declarative: it describes what to call and with which parameters.
Step 4: Response Parsing and Natural Language Output
The API returns a JSON response. The AI parses it according to the response schema defined in the endpoint specification. It then synthesizes a natural-language reply:
"The current order book for AAPL.US shows a bid-ask spread of $0.02. The top bid is at $150.25 with 12,400 shares available, while the top ask sits at $150.27 with 8,200 shares. The buy/sell pressure ratio is 1.51 — indicating moderate bid-side dominance."
The AI computed the pressure ratio (12,400 ÷ 8,200 ≈ 1.51) from the raw bid/ask sizes in the response. This is not a field returned by the API — it is a derived metric the AI calculates from the data it received, guided by the derived_metrics section of the conventions block:
conventions:
derived_metrics:
pressure_ratio:
formula: "sum(bid_sizes, top_n) / sum(ask_sizes, top_n)"
description: "Ratio of bid-side liquidity to ask-side liquidity. Values >1 indicate buying pressure; <1 indicates selling pressure."
Multi-Turn Conversation: State Management and Parameter Carrying
A single API call rarely constitutes a complete answer. A quant researcher asking "Show me the order book imbalance for NVIDIA over the last hour" implicitly needs a time-series of depth snapshots — not a single snapshot. The SKILL protocol handles multi-turn conversations through an explicit state management model.
Conversation State Object
The AI maintains a conversation state object throughout the session:
{
"current_symbol": "NVDA.US",
"current_intent": "get_realtime_depth",
"window_start": 1709836800000,
"window_end": 1709840400000,
"sampling_interval": 60000,
"accumulated_data": []
}
When the user says "Now show me TSMC" in the next turn, the AI carries forward the sampling interval and window parameters from the previous state — but resets current_symbol to TSMC.TW and clears accumulated_data.
The state object is defined in the conventions block:
conventions:
state_management:
carries_forward:
- window_duration
- sampling_interval
- limit
- depth_channel
resets_on_new_symbol:
- accumulated_data
resets_on_new_intent:
- accumulated_data
- window_start
- window_end
This tells the AI precisely what to remember between turns and what to discard. Without this explicit specification, an AI model might carry forward stale parameters — for example, using the wrong time window when the user switches symbols but keeps the same analytical goal.
Clarification and Confirmation Flows
The conventions block also defines when the AI must ask for confirmation:
conventions:
confirm_ambiguous_params:
- symbol (when multiple exchanges list the same name)
- time_range (when no duration is specified and the user asks for "historical data")
- limit (when the user requests "all levels" — ambiguous between 50 and the maximum)
When a user asks "Show me the order book," the AI checks whether a symbol was specified. If not, it asks: "Which symbol are you interested in? For example, AAPL.US for Apple, or BTCUSDT.BT for Bitcoin."
This is not a failure of intent recognition — it is a deliberate guardrail. The SKILL protocol treats ambiguity as a signal to request clarification rather than to guess and potentially return the wrong data.
Production-Grade Integration Code
The following code demonstrates a complete integration that reads the skill.md specification, builds the function calling schema, and handles the full request-response cycle with production-grade resilience.
import os
import json
import time
import random
import requests
from typing import Any
# Configuration
SKILL_MD_URL = "http://skill.md/"
TICKDB_BASE_URL = "https://api.tickdb.ai/v1"
API_KEY = os.environ.get("TICKDB_API_KEY")
REQUEST_TIMEOUT = (3.05, 10) # Connect timeout, read timeout
# ─── Step 1: Fetch and cache the skill specification ────────────────────────
def fetch_skill_spec(url: str, cache_ttl_seconds: int = 3600) -> dict:
"""
Fetch the skill.md specification and cache it for repeated use.
⚠️ In production, this cache should be a Redis entry or an in-memory
TTL cache with thread-safe access. Using a module-level dict for
caching in a multi-threaded environment creates race conditions.
"""
cache_key = f"_skill_spec_cache_{url}"
if hasattr(fetch_skill_spec, cache_key):
cached = getattr(fetch_skill_spec, cache_key)
if time.time() - cached["fetched_at"] < cache_ttl_seconds:
return cached["spec"]
response = requests.get(url, timeout=REQUEST_TIMEOUT)
response.raise_for_status()
spec = response.json()
setattr(fetch_skill_spec, cache_key, {
"spec": spec,
"fetched_at": time.time()
})
return spec
# ─── Step 2: Parse the skill spec into function schemas ──────────────────────
def extract_function_schemas(skill_spec: dict) -> list[dict]:
"""
Extract all function definitions from the skill.md spec.
Returns a list of function schemas compatible with LLM Function Calling.
"""
functions = []
for intent in skill_spec.get("intents", []):
endpoint_def = skill_spec.get("endpoints", {}).get(intent["name"])
if endpoint_def:
functions.append({
"type": "function",
"function": {
"name": endpoint_def["name"],
"description": endpoint_def["description"],
"parameters": endpoint_def["parameters"]
}
})
return functions
# ─── Step 3: Execute a function call against TickDB ─────────────────────────
def execute_tickdb_call(function_name: str, arguments: dict, skill_spec: dict) -> dict:
"""
Execute a TickDB API call based on the function name and arguments.
⚠️ This function handles rate limits with exponential backoff + jitter.
For HFT workloads, replace requests with aiohttp/asyncio and
implement a token-bucket rate limiter at the caller level.
"""
endpoint_map = {
entry["function"]["name"]: entry["function"]
for entry in extract_function_schemas(skill_spec)
}
if function_name not in endpoint_map:
raise ValueError(f"Unknown function: {function_name}")
endpoint = endpoint_map[function_name]
# Map function name to actual API path
path_map = skill_spec.get("endpoint_paths", {})
path = path_map.get(function_name, "")
url = f"{TICKDB_BASE_URL}{path}"
headers = {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
}
# Retry loop with exponential backoff and jitter
max_retries = 5
base_delay = 1.0
max_delay = 30.0
for attempt in range(max_retries):
response = requests.post(
url,
headers=headers,
json={"symbol": arguments.get("symbol"), **arguments},
timeout=REQUEST_TIMEOUT
)
response_data = response.json() if response.headers.get("Content-Type", "").startswith("application/json") else {}
code = response_data.get("code", 0)
if code == 0:
return response_data.get("data", {})
if code == 3001:
retry_after = int(response.headers.get("Retry-After", base_delay * (2 ** attempt)))
jitter = random.uniform(0, retry_after * 0.1)
sleep_time = retry_after + jitter
print(f"Rate limited (3001). Retrying in {sleep_time:.2f}s (attempt {attempt + 1}/{max_retries})")
time.sleep(sleep_time)
continue
if code in (1001, 1002):
raise ValueError(f"Authentication failed (code {code}): check your TICKDB_API_KEY env var")
if code == 2002:
raise KeyError(f"Symbol {arguments.get('symbol')} not found — verify via /v1/symbols/available")
raise RuntimeError(f"Unexpected error {code}: {response_data.get('message')}")
raise RuntimeError(f"Max retries ({max_retries}) exceeded for {function_name}")
# ─── Step 4: Derive pressure ratio from depth data ──────────────────────────
def compute_pressure_ratio(depth_data: dict, top_n: int = 5) -> float:
"""
Compute buy/sell pressure ratio from order book depth data.
Formula: Σ(bid sizes, top N levels) / Σ(ask sizes, top N levels)
>1.0 = buying pressure; <1.0 = selling pressure; ~1.0 = balanced
"""
bids = depth_data.get("bids", [])
asks = depth_data.get("asks", [])
bid_volume = sum(size for _, size in bids[:top_n])
ask_volume = sum(size for _, size in asks[:top_n])
if ask_volume == 0:
return float('inf') if bid_volume > 0 else 0.0
return round(bid_volume / ask_volume, 2)
# ─── Main execution example ───────────────────────────────────────────────────
if __name__ == "__main__":
spec = fetch_skill_spec(SKILL_MD_URL)
schemas = extract_function_schemas(spec)
print(f"Loaded {len(schemas)} function schemas from skill.md")
# Execute a depth query
depth_data = execute_tickdb_call(
"tickdb_get_depth",
{"symbol": "AAPL.US", "limit": 10, "depth_channel": "depth"},
spec
)
pressure = compute_pressure_ratio(depth_data, top_n=5)
print(f"Buy/sell pressure ratio: {pressure}")
Engineering notes embedded in the code:
- The cache TTL should be managed by Redis or a thread-safe in-memory store in a production deployment. Module-level attributes are not thread-safe.
- For high-frequency trading (HFT) workloads, replace the synchronous
requestslibrary withaiohttpandasyncio. This code is designed for low-to-medium frequency usage — intraday analysis, not millisecond-level signal generation. - The
compute_pressure_ratiofunction is not part of the API response. It is a derived metric computed client-side from the raw bid/ask arrays. If you need this metric computed server-side, the SKILL protocol'sderived_metricsconventions serve as a specification for how the AI should calculate it.
SKILL Protocol versus Alternative Approaches
| Dimension | Raw API docs (Swagger/OpenAPI) | Custom fine-tuned LLM | SKILL Protocol |
|---|---|---|---|
| Maintenance | Requires Swagger spec sync | Requires re-training on API changes | Update skill.md file; AI re-reads on next cache refresh |
| Cross-model compatibility | Tool-agnostic, but needs adapter | Model-specific | Works with any Function Calling-capable LLM (OpenAI, Anthropic, local models) |
| Intent recognition | Not included | Learned during training | Explicit trigger phrases + conventions |
| Error handling | Documented in prose | Learned | Explicit error code mapping in JSON |
| Multi-turn state | DIY | Depends on context window | Explicit state management conventions |
| Cost | Free | High (fine-tuning compute) | Near-zero (no training; cache reads only) |
The SKILL protocol is not a replacement for OpenAPI specs — it is a higher-abstraction layer built on top of them. The OpenAPI spec tells the machine everything. The SKILL protocol tells the AI assistant how to think about the data.
Deployment: Who Needs Which Configuration
| User segment | Deployment mode | Key configuration |
|---|---|---|
| Individual quant developer | Local AI assistant (Claude Desktop, Cursor, Copilot) + TickDB SKILL | Install skill.md locally; configure API key in env vars |
| Trading team | Team-hosted LLM + shared skill.md cache | Deploy skill.md on internal CDN; cache TTL of 3600s; team-wide rate limit coordination |
| Institutional | TickDB Enterprise + managed SKILL + custom intent extensions | Custom alias mappings for proprietary tickers; dedicated rate limit pool |
| AI tool developer | Build on top of SKILL spec | Use function schemas as the canonical interface contract; no need to reverse-engineer from code |
For most individual developers, the deployment path is simple: point your AI assistant's SKILL configuration at http://skill.md/, set your TICKDB_API_KEY environment variable, and start querying.
Key Takeaways
The SKILL protocol is a thin, structured layer between your natural-language intent and TickDB's API surface. Its power lies not in complexity but in explicitness — every parameter, every response shape, every error code, and every conversational convention is declared in machine-readable form.
For quant developers building AI-augmented workflows, this means three concrete benefits:
- Reliable Function Calling: Your AI assistant will not hallucinate endpoint names or parameter structures. The function schema is the ground truth.
- Resilient error handling: Error codes are mapped to actionable messages. The AI can recover from rate limits, auth failures, and invalid symbols without human intervention.
- Composable workflows: Multi-turn conversations carry state correctly. Switching symbols or adjusting time windows does not require re-explaining the analytical goal from scratch.
The protocol is maintained at http://skill.md/ and updated whenever TickDB's API surface changes. Any compatible AI assistant re-reads the specification on cache expiration — no fine-tuning, no plugin updates, no retraining.
Next Steps
If you're an individual developer, install the tickdb-market-data SKILL in your AI assistant's marketplace (e.g., Claude Desktop, Cursor, or your preferred local LLM), set the TICKDB_API_KEY environment variable, and try asking: "What's the order book imbalance for NVDA.US right now?"
If you're a team lead evaluating AI-assisted trading workflows, review the full skill.md specification and assess how the Function Calling schema aligns with your existing data pipeline. The SKILL protocol's state management conventions are particularly relevant for teams building multi-turn analysis tools.
If you want to extend the protocol for proprietary use cases, the conventions block supports custom intent definitions, symbol aliases, and derived metric formulas. Contact enterprise@tickdb.ai to discuss custom SKILL extensions for institutional deployments.
If you're an AI tooling developer, the function schemas extracted from skill.md provide a canonical interface contract that is vendor-neutral. Use these schemas as the foundation for building TickDB-compatible integrations without reverse-engineering from code samples.
This article does not constitute investment advice. Market data provided by TickDB is for informational purposes only. API capabilities, rate limits, and supported asset classes are subject to change. Always verify current specifications at tickdb.ai before building production integrations.