58a1dc2308
- Deleted the .coverage file to clean up the repository. - Updated .gitignore to ensure .coverage and .coverage.* files are ignored in future commits. - Revised README.md to enhance clarity and conciseness regarding the library's capabilities and performance. - Improved documentation for the MCP server, emphasizing its expanded functionality and integration with clients.
204 lines
4.9 KiB
Markdown
204 lines
4.9 KiB
Markdown
# Agentic Workflow and Tools
|
|
|
|
ferro-ta provides stable tool wrappers and a workflow orchestrator that make
|
|
it easy to integrate with AI agents, LangChain, LlamaIndex, or any
|
|
framework that supports function calling.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The agentic API consists of two modules:
|
|
|
|
| Module | Purpose |
|
|
|--------|---------|
|
|
| `ferro_ta.tools` | Stable, documented functions for agent wrapping |
|
|
| `ferro_ta.workflow` | End-to-end pipeline: indicators → strategy → alerts |
|
|
|
|
---
|
|
|
|
## `ferro_ta.tools` — Tool wrappers
|
|
|
|
```python
|
|
from ferro_ta.tools import compute_indicator, run_backtest, list_indicators, describe_indicator
|
|
import numpy as np
|
|
|
|
close = np.cumprod(1 + np.random.default_rng(0).normal(0, 0.01, 200)) * 100
|
|
|
|
# Compute any indicator by name
|
|
sma = compute_indicator("SMA", close, timeperiod=20)
|
|
rsi = compute_indicator("RSI", close, timeperiod=14)
|
|
bb = compute_indicator("BBANDS", close, timeperiod=20) # returns dict
|
|
|
|
# Run a backtest
|
|
summary = run_backtest("rsi_30_70", close)
|
|
print(f"Final equity: {summary['final_equity']:.4f}")
|
|
print(f"Trades: {summary['n_trades']}")
|
|
|
|
# List all indicators
|
|
names = list_indicators() # sorted list of strings
|
|
|
|
# Describe an indicator (returns first paragraph of docstring)
|
|
desc = describe_indicator("RSI")
|
|
```
|
|
|
|
### Function signatures
|
|
|
|
```python
|
|
def compute_indicator(name: str, *args, **kwargs) -> ndarray | dict:
|
|
...
|
|
|
|
def run_backtest(strategy: str, close, commission_per_trade=0.0, slippage_bps=0.0, **kwargs) -> dict:
|
|
...
|
|
|
|
def list_indicators() -> list[str]:
|
|
...
|
|
|
|
def describe_indicator(name: str) -> str:
|
|
...
|
|
```
|
|
|
|
---
|
|
|
|
## `ferro_ta.workflow` — End-to-end pipeline
|
|
|
|
```python
|
|
from ferro_ta.workflow import Workflow
|
|
import numpy as np
|
|
|
|
rng = np.random.default_rng(42)
|
|
close = np.cumprod(1 + rng.normal(0, 0.01, 200)) * 100
|
|
|
|
result = (
|
|
Workflow()
|
|
.add_indicator("sma_20", "SMA", timeperiod=20)
|
|
.add_indicator("rsi_14", "RSI", timeperiod=14)
|
|
.add_strategy("rsi_30_70")
|
|
.add_alert("rsi_14", level=30.0, direction=-1) # alert when RSI crosses below 30
|
|
.run(close)
|
|
)
|
|
|
|
print(result.keys())
|
|
# dict_keys(['sma_20', 'rsi_14', 'backtest', 'alert_rsi_14_30_-1'])
|
|
```
|
|
|
|
### Functional interface
|
|
|
|
```python
|
|
from ferro_ta.workflow import run_pipeline
|
|
|
|
result = run_pipeline(
|
|
close,
|
|
indicators={
|
|
"sma_20": {"name": "SMA", "timeperiod": 20},
|
|
"rsi_14": {"name": "RSI", "timeperiod": 14},
|
|
},
|
|
strategy="rsi_30_70",
|
|
alert_indicator="rsi_14",
|
|
alert_level=30.0,
|
|
alert_direction=-1,
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## LangChain integration
|
|
|
|
Wrap the tools as LangChain `Tool` objects:
|
|
|
|
```python
|
|
from langchain.tools import Tool
|
|
from ferro_ta.tools import compute_indicator, run_backtest, list_indicators
|
|
import numpy as np
|
|
import json
|
|
|
|
def _compute_tool(input_str: str) -> str:
|
|
"""Parse JSON input and compute an indicator."""
|
|
args = json.loads(input_str)
|
|
name = args.pop("name")
|
|
close = np.asarray(args.pop("close"), dtype=np.float64)
|
|
result = compute_indicator(name, close, **args)
|
|
if isinstance(result, dict):
|
|
return json.dumps({k: v.tolist() for k, v in result.items()})
|
|
return json.dumps(result.tolist())
|
|
|
|
def _backtest_tool(input_str: str) -> str:
|
|
args = json.loads(input_str)
|
|
close = np.asarray(args.pop("close"), dtype=np.float64)
|
|
strategy = args.pop("strategy", "rsi_30_70")
|
|
summary = run_backtest(strategy, close, **args)
|
|
return json.dumps(summary)
|
|
|
|
tools = [
|
|
Tool(
|
|
name="compute_indicator",
|
|
func=_compute_tool,
|
|
description=(
|
|
'Compute a technical indicator. Input JSON: {"name": "SMA", '
|
|
'"close": [...], "timeperiod": 14}'
|
|
),
|
|
),
|
|
Tool(
|
|
name="run_backtest",
|
|
func=_backtest_tool,
|
|
description=(
|
|
'Run a backtest. Input JSON: {"strategy": "rsi_30_70", '
|
|
'"close": [...]}'
|
|
),
|
|
),
|
|
Tool(
|
|
name="list_indicators",
|
|
func=lambda _: json.dumps(list_indicators()),
|
|
description="List all available indicator names. No input required.",
|
|
),
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## Scheduling
|
|
|
|
### Run once
|
|
|
|
```python
|
|
python examples/run_workflow.py
|
|
```
|
|
|
|
### Run every N minutes (cron)
|
|
|
|
Add to your crontab:
|
|
|
|
```
|
|
*/15 * * * * /usr/bin/python /path/to/examples/run_workflow.py >> /var/log/ferro_ta.log 2>&1
|
|
```
|
|
|
|
### Run on a schedule with `schedule` library
|
|
|
|
```python
|
|
import schedule
|
|
import time
|
|
|
|
def job():
|
|
import numpy as np
|
|
from ferro_ta.workflow import run_pipeline
|
|
# fetch latest prices here ...
|
|
close = np.ones(100) # replace with real data
|
|
result = run_pipeline(close, indicators={"rsi": {"name": "RSI", "timeperiod": 14}})
|
|
print(result)
|
|
|
|
schedule.every(15).minutes.do(job)
|
|
while True:
|
|
schedule.run_pending()
|
|
time.sleep(1)
|
|
```
|
|
|
|
---
|
|
|
|
## See also
|
|
|
|
- `ferro_ta.tools` — module source.
|
|
- `ferro_ta.workflow` — module source.
|
|
- `docs/mcp.md` — MCP server for MCP-compatible clients.
|
|
- `ferro_ta.backtest` — backtest harness.
|
|
- `ferro_ta.registry` — indicator registry.
|