Skip to content

Commit 6ede61d

Browse files
committed
Add crypto analysis use-cases tool
1 parent 247451b commit 6ede61d

4 files changed

Lines changed: 314 additions & 0 deletions

File tree

lib/sanbase/mcp/server.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ defmodule Sanbase.MCP.Server do
2727
# Register Screener tool
2828
component(Sanbase.MCP.AssetsByMetricTool)
2929

30+
# Register Use Cases Catalog tool
31+
component(Sanbase.MCP.UseCasesCatalogTool)
32+
3033
if Application.compile_env(:sanbase, :env) in [:test, :dev] do
3134
IO.puts("Defining the extra MCP Server tools used in dev and test")
3235
# Some tools are enabled only in dev mode so we can test things during development
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
defmodule Sanbase.MCP.UseCasesCatalog do
2+
@moduledoc """
3+
Catalog of analytical use cases with execution steps.
4+
Each use case includes plain English instructions referencing specific MCP tools.
5+
"""
6+
7+
def all_use_cases do
8+
[
9+
identify_market_tops()
10+
]
11+
end
12+
13+
defp identify_market_tops do
14+
%{
15+
id: "identify_market_tops",
16+
title: "Identify Market Tops Using Santiment Metrics",
17+
description: """
18+
Multi-signal approach combining social volume, sentiment, network activity,
19+
and on-chain metrics to identify potential market tops. This framework
20+
emphasizes using multiple indicators together rather than relying on any
21+
single metric.
22+
""",
23+
category: "market_timing",
24+
difficulty: "intermediate",
25+
estimated_time: "5-10 minutes",
26+
applies_to: "Any crypto asset with sufficient data history",
27+
steps: """
28+
Step 1: Check for social volume spikes during rallies
29+
Use the fetch_metric_data tool with these parameters:
30+
- metric: "social_volume_total"
31+
- slugs: [your target asset, e.g., "bitcoin"]
32+
- time_period: "30d"
33+
- interval: "1d"
34+
35+
Look for extreme spikes in social volume (3x or more above the 30-day baseline)
36+
that occur during price rallies. This is especially reliable for mid-cap and
37+
small-cap coins. Extreme social attention during rallies often marks local tops.
38+
39+
Step 2: Analyze sentiment for overbought crowd conditions
40+
Use the fetch_metric_data tool to check positive sentiment:
41+
- metric: "sentiment_positive_total"
42+
- slugs: [your target asset]
43+
- time_period: "7d"
44+
- interval: "1d"
45+
46+
When the crowd is overly bullish, it can indicate a top. Look for:
47+
- Positive sentiment representing > 70% of total mentions
48+
- This elevated sentiment sustained for 3+ consecutive days
49+
50+
Optionally also fetch "sentiment_negative_total" and "sentiment_balance_total"
51+
for a more complete picture. Strongly positive sentiment balance (>+0.5)
52+
during rallies is a bearish signal.
53+
54+
Step 3: Check for network activity vs. price behavior divergence
55+
Use the fetch_metric_data tool to check network activity:
56+
- metric: "daily_active_addresses"
57+
- slugs: [your target asset]
58+
- time_period: "30d"
59+
- interval: "1d"
60+
61+
Compare the trend in daily active addresses with price movement. If price has
62+
risen 20% or more but daily active addresses remain flat or are declining,
63+
this divergence signals a potential top. Healthy rallies are supported by
64+
increasing on-chain activity.
65+
66+
Optionally also check "network_growth" (new addresses) for additional confirmation.
67+
68+
Step 4: Check MVRV ratio for overbought valuation
69+
Use the fetch_metric_data tool to check valuation:
70+
- metric: "mvrv_usd"
71+
- slugs: [your target asset]
72+
- time_period: "90d"
73+
- interval: "1d"
74+
75+
MVRV (Market Value to Realized Value) ratio indicates whether holders are
76+
in profit. High MVRV suggests many holders are sitting on gains and may
77+
take profits. Thresholds vary by asset:
78+
- Bitcoin: MVRV > 2.5 indicates overbought (bull market: > 3.5)
79+
- Other assets: Research historical MVRV levels for the specific asset
80+
81+
Step 5: Check Mean Dollar Invested Age for long-term holder distribution
82+
Use the fetch_metric_data tool to check coin age:
83+
- metric: "mean_dollar_invested_age"
84+
- slugs: [your target asset]
85+
- time_period: "180d"
86+
- interval: "1d"
87+
88+
MDIA tracks how long funds have stayed in addresses. Rising MDIA indicates
89+
hodler accumulation, while dips suggest movement of previously idle coins.
90+
91+
Every major Bitcoin top has been accompanied by a significant drop in MDIA
92+
as long-term holders distribute coins. Look for sharp drops (>10%) during
93+
price rallies. This is particularly relevant for Bitcoin and major assets
94+
with long history.
95+
""",
96+
interpretation: """
97+
## How to Interpret Combined Signals
98+
99+
This framework uses multiple indicators. Assess the overall picture:
100+
101+
**Strong Top Signal (High Confidence)**
102+
When you observe 4-5 of these conditions together:
103+
- Social volume spike 3x+ baseline during rally
104+
- Positive sentiment > 70% sustained 3+ days
105+
- Network activity declining while price rises 20%+
106+
- MVRV > 2.5 (or asset-specific threshold)
107+
- MDIA drops > 10% during rally
108+
109+
Action: Consider taking profits or tightening stop losses
110+
111+
**Moderate Top Signal**
112+
When 2-3 bearish signals are present with mixed signals across categories.
113+
114+
Action: Monitor closely, consider reducing position size
115+
116+
**Weak/No Top Signal**
117+
When 0-1 bearish signals present and most metrics show healthy conditions.
118+
119+
Action: Continue holding, no immediate concern
120+
121+
## Important Context
122+
- **Small/mid-cap coins**: Social volume spikes are more reliable indicators
123+
- **Large-cap coins (BTC, ETH)**: MDIA and network activity more important
124+
- **Bull markets**: Higher thresholds needed (MVRV > 3.5 for BTC)
125+
- **Bear markets**: Lower thresholds (MVRV > 1.5 may indicate local top)
126+
- **No single metric**: Always combine multiple data points for robust analysis
127+
128+
## Setting Up Alerts
129+
On Sanbase, you can subscribe to alerts for surges in social volume to catch
130+
potential corrections early. Use the Social Trends tool to visualize momentum.
131+
""",
132+
references: [
133+
%{
134+
title: "Getting started with Santiment",
135+
url: "https://academy.santiment.net/santiment-introduction/"
136+
},
137+
%{
138+
title: "Getting started for traders",
139+
url: "https://academy.santiment.net/for-traders/"
140+
},
141+
%{
142+
title: "Understanding Short-Term Market Trends",
143+
url:
144+
"https://academy.santiment.net/education-and-use-cases/understanding-short-term-market-trends/"
145+
},
146+
%{
147+
title: "Sentiment metrics",
148+
url: "https://academy.santiment.net/metrics/sentiment-metrics/"
149+
}
150+
]
151+
}
152+
end
153+
end
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
defmodule Sanbase.MCP.UseCasesCatalogTool do
2+
@moduledoc """
3+
**CALL THIS TOOL FIRST** when answering crypto analysis questions to check if the query
4+
matches any predefined analytical strategies.
5+
6+
This tool returns a catalog of proven analytical use cases with complete step-by-step
7+
execution instructions. Each use case is a ready-to-use analytical recipe that:
8+
- Identifies the analytical goal (e.g., "Is this asset near a top?")
9+
- Provides 5-10 detailed steps referencing specific MCP tools to call
10+
- Explains how to interpret the combined results
11+
- Ensures comprehensive multi-signal analysis following best practices
12+
13+
## When to Use This Tool
14+
15+
**ALWAYS call this tool FIRST** when the user asks questions like:
16+
- "Is [asset] near a top?" or "Is [asset] overbought?"
17+
- "Should I buy/sell [asset]?"
18+
- "Is [asset] in an accumulation zone?"
19+
- "What's the market sentiment for [asset]?"
20+
- Any question about market timing, price predictions, or trading decisions
21+
22+
## How to Use This Tool
23+
24+
1. **Call this tool first** (no parameters needed)
25+
2. **Compare** the user's query to the available use case titles and descriptions
26+
3. **If a use case matches**: Follow the step-by-step instructions provided
27+
- Each step tells you exactly which tool to call and with what parameters
28+
- Execute the steps in order, gathering data from each
29+
- Synthesize results using the interpretation guide
30+
4. **If no use case matches**: Proceed with ad-hoc analysis using individual tools
31+
32+
## Benefits of Using This Tool First
33+
34+
- **Comprehensive analysis**: Use cases combine multiple signals (not just one metric)
35+
- **Best practices**: Strategies are based on proven analytical frameworks
36+
- **Time-saving**: Get a complete analytical recipe instead of guessing which metrics to use
37+
- **Better answers**: Multi-signal approaches provide more reliable insights
38+
39+
## Example Use Cases Available
40+
41+
- **Identify Market Tops**: Combines social volume, sentiment, network activity, MVRV,
42+
and mean dollar age to detect potential tops with high confidence
43+
- More use cases will be added over time
44+
45+
## Response Format
46+
47+
Returns a list of use cases, each containing:
48+
- `title`: What analytical question this use case answers
49+
- `steps`: Plain text instructions with specific tool calls and parameters
50+
- `interpretation`: How to combine and interpret the results
51+
"""
52+
53+
use Anubis.Server.Component, type: :tool
54+
55+
alias Anubis.Server.Response
56+
alias Sanbase.MCP.UseCasesCatalog
57+
58+
schema do
59+
end
60+
61+
@impl true
62+
def execute(_params, frame) do
63+
use_cases = UseCasesCatalog.all_use_cases()
64+
65+
simplified_use_cases =
66+
Enum.map(use_cases, fn use_case ->
67+
%{
68+
title: use_case.title,
69+
steps: use_case.steps,
70+
interpretation: use_case.interpretation
71+
}
72+
end)
73+
74+
{:reply, Response.json(Response.tool(), simplified_use_cases), frame}
75+
end
76+
end
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
defmodule Sanbase.MCP.UseCasesCatalogToolTest do
2+
use SanbaseWeb.ConnCase, async: false
3+
4+
alias Sanbase.MCP.UseCasesCatalog
5+
6+
describe "UseCasesCatalog" do
7+
test "returns all use cases with required fields" do
8+
use_cases = UseCasesCatalog.all_use_cases()
9+
10+
assert is_list(use_cases)
11+
assert length(use_cases) > 0
12+
13+
Enum.each(use_cases, fn use_case ->
14+
assert use_case.title
15+
assert is_binary(use_case.steps)
16+
assert String.length(use_case.steps) > 100
17+
assert use_case.interpretation
18+
assert String.contains?(use_case.steps, "Step 1")
19+
end)
20+
end
21+
22+
test "steps mention tools to use" do
23+
use_cases = UseCasesCatalog.all_use_cases()
24+
25+
Enum.each(use_cases, fn use_case ->
26+
assert String.contains?(use_case.steps, "tool") or
27+
String.contains?(use_case.steps, "fetch_metric_data")
28+
end)
29+
end
30+
end
31+
32+
describe "UseCasesCatalogTool execute/2" do
33+
test "returns simplified list of use cases" do
34+
frame = %{assigns: %{}}
35+
36+
{:reply, response, _frame} = Sanbase.MCP.UseCasesCatalogTool.execute(%{}, frame)
37+
38+
assert response.content
39+
[%{"text" => json_text, "type" => "text"}] = response.content
40+
use_cases = Jason.decode!(json_text)
41+
42+
assert is_list(use_cases)
43+
assert length(use_cases) > 0
44+
end
45+
46+
test "returned use cases have only title, steps, and interpretation" do
47+
frame = %{assigns: %{}}
48+
49+
{:reply, response, _frame} = Sanbase.MCP.UseCasesCatalogTool.execute(%{}, frame)
50+
51+
[%{"text" => json_text, "type" => "text"}] = response.content
52+
use_cases = Jason.decode!(json_text)
53+
use_case = List.first(use_cases)
54+
55+
assert use_case["title"]
56+
assert use_case["steps"]
57+
assert use_case["interpretation"]
58+
59+
assert is_binary(use_case["steps"])
60+
assert String.length(use_case["steps"]) > 100
61+
62+
refute Map.has_key?(use_case, "id")
63+
refute Map.has_key?(use_case, "description")
64+
refute Map.has_key?(use_case, "category")
65+
refute Map.has_key?(use_case, "metadata")
66+
end
67+
68+
test "steps is plain text not a list" do
69+
frame = %{assigns: %{}}
70+
71+
{:reply, response, _frame} = Sanbase.MCP.UseCasesCatalogTool.execute(%{}, frame)
72+
73+
[%{"text" => json_text, "type" => "text"}] = response.content
74+
use_cases = Jason.decode!(json_text)
75+
76+
Enum.each(use_cases, fn use_case ->
77+
assert is_binary(use_case["steps"])
78+
refute is_list(use_case["steps"])
79+
end)
80+
end
81+
end
82+
end

0 commit comments

Comments
 (0)