docs: add step-by-step gate architecture to SKILL.md
Adds a complete blueprint for building the lightweight precheck / external-gate system that reduces model cost by 80-95% while keeping deep analysis quality intact. Includes file layout, state schema, pseudocode for precheck and external gate, and cron config examples.
This commit is contained in:
150
SKILL.md
150
SKILL.md
@@ -115,6 +115,156 @@ This pattern preserves Telegram auto-delivery from Hermes cron while reducing mo
|
||||
- Add a file lock around the external gate script so overlapping system-cron invocations cannot double-trigger.
|
||||
- Rotate `~/.coinhunter/logs/external_gate.log` with `logrotate` (daily, keep ~14 compressed copies, `copytruncate`) and schedule the rotation a few minutes after the fallback Hermes cron run so they do not overlap.
|
||||
|
||||
### Building your own gate (step-by-step)
|
||||
If you want to replicate this low-cost trigger architecture, here is a complete blueprint.
|
||||
|
||||
#### 1. File layout
|
||||
Create these files under `~/.hermes/scripts/` and state under `~/.coinhunter/state/`:
|
||||
```
|
||||
~/.hermes/scripts/
|
||||
coinhunter_precheck.py # lightweight threshold evaluator
|
||||
coinhunter_external_gate.py # optional system-crontab wrapper
|
||||
|
||||
~/.coinhunter/state/
|
||||
precheck_state.json # last snapshot + trigger flags
|
||||
external_gate.lock # flock file for external gate
|
||||
```
|
||||
|
||||
#### 2. State schema (`precheck_state.json`)
|
||||
```json
|
||||
{
|
||||
"last_positions_hash": "sha256_of_positions_json",
|
||||
"last_top_candidates_hash": "sha256_of_top_5_coins",
|
||||
"last_btc_regime": "bullish|neutral|bearish",
|
||||
"last_deep_analysis_at": "2026-04-15T11:00:00Z",
|
||||
"free_usdt": 12.50,
|
||||
"account_total_usdt": 150.00,
|
||||
"run_requested_at": null,
|
||||
"run_acknowledged_at": "2026-04-15T11:05:00Z",
|
||||
"volatility_session": "low|medium|high",
|
||||
"staleness_hours": 2.0
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Precheck script logic (pseudocode)
|
||||
```python
|
||||
import json, hashlib, os, math
|
||||
from datetime import datetime, timezone
|
||||
|
||||
STATE_PATH = os.path.expanduser("~/.coinhunter/state/precheck_state.json")
|
||||
POSITIONS_PATH = os.path.expanduser("~/.coinhunter/positions.json")
|
||||
|
||||
def load_json(path):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_json(path, obj):
|
||||
tmp = path + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
json.dump(obj, f, indent=2)
|
||||
os.replace(tmp, path)
|
||||
|
||||
def compute_hash(obj):
|
||||
return hashlib.sha256(json.dumps(obj, sort_keys=True).encode()).hexdigest()[:16]
|
||||
|
||||
def adaptive_thresholds(account_total_usdt, volatility_session):
|
||||
# Micro accounts get wider thresholds; high volatility narrows them
|
||||
base_price = 0.03 if account_total_usdt >= 100 else 0.08
|
||||
base_pnl = 0.05 if account_total_usdt >= 100 else 0.12
|
||||
vol_mult = {"low": 1.2, "medium": 1.0, "high": 0.7}.get(volatility_session, 1.0)
|
||||
return base_price * vol_mult, base_pnl * vol_mult
|
||||
|
||||
def should_analyze():
|
||||
state = load_json(STATE_PATH) if os.path.exists(STATE_PATH) else {}
|
||||
positions = load_json(POSITIONS_PATH)
|
||||
# ... fetch current tickers, BTC regime, free USDT here ...
|
||||
new_pos_hash = compute_hash(positions.get("positions", []))
|
||||
new_btc_regime = "neutral" # replace with actual analysis
|
||||
new_free = positions.get("balances", {}).get("USDT", {}).get("free", 0)
|
||||
total = positions.get("account_total_usdt", 0)
|
||||
volatility = "medium" # replace with actual session metric
|
||||
|
||||
price_thr, pnl_thr = adaptive_thresholds(total, volatility)
|
||||
|
||||
triggers = []
|
||||
if new_pos_hash != state.get("last_positions_hash"):
|
||||
triggers.append("position_change")
|
||||
if new_btc_regime != state.get("last_btc_regime"):
|
||||
triggers.append("btc_regime_change")
|
||||
# ... check per-position price/PnL drift vs thresholds ...
|
||||
# ... check candidate leadership change (skip if free_usdt < min_actionable) ...
|
||||
staleness = (datetime.now(timezone.utc) - datetime.fromisoformat(state.get("last_deep_analysis_at","2000-01-01T00:00:00+00:00"))).total_seconds() / 3600.0
|
||||
max_staleness = 4.0 if total >= 100 else 8.0
|
||||
if staleness >= max_staleness:
|
||||
triggers.append("staleness")
|
||||
|
||||
decision = bool(triggers)
|
||||
state.update({
|
||||
"last_positions_hash": new_pos_hash,
|
||||
"last_btc_regime": new_btc_regime,
|
||||
"free_usdt": new_free,
|
||||
"account_total_usdt": total,
|
||||
"volatility_session": volatility,
|
||||
"staleness_hours": staleness,
|
||||
})
|
||||
if decision:
|
||||
state["run_requested_at"] = datetime.now(timezone.utc).isoformat()
|
||||
save_json(STATE_PATH, state)
|
||||
return {"should_analyze": decision, "triggers": triggers, "state": state}
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = should_analyze()
|
||||
print(json.dumps(result))
|
||||
```
|
||||
|
||||
#### 4. Hermes cron job configuration
|
||||
Attach the precheck script as the `script` field of the cron job so its JSON output is injected into the prompt:
|
||||
```json
|
||||
{
|
||||
"id": "coinhunter-trade",
|
||||
"schedule": "*/15 * * * *",
|
||||
"prompt": "You are Coin Hunter. If the injected context says should_analyze=false, respond with exactly [SILENT] and do nothing. Otherwise, read ~/.coinhunter/positions.json, run the scientific checklist, decide HOLD/SELL/REBALANCE/BUY, and execute via smart_executor.py. After finishing, run ~/.hermes/scripts/coinhunter_precheck.py --ack to clear the trigger.",
|
||||
"script": "~/.hermes/scripts/coinhunter_precheck.py",
|
||||
"deliver": "telegram",
|
||||
"model": "kimi-for-coding"
|
||||
}
|
||||
```
|
||||
|
||||
Add an `--ack` handler to the precheck script (or a separate ack script) that sets `run_acknowledged_at` and clears `run_requested_at` so the gate does not re-fire until the next true trigger.
|
||||
|
||||
#### 5. External gate (optional, for even lower cost)
|
||||
If you want to run the precheck every 5 minutes without waking Hermes at all:
|
||||
|
||||
`coinhunter_external_gate.py` pseudocode:
|
||||
```python
|
||||
import fcntl, os, subprocess, json, sys
|
||||
|
||||
LOCK_PATH = os.path.expanduser("~/.coinhunter/state/external_gate.lock")
|
||||
PRECHECK = os.path.expanduser("~/.hermes/scripts/coinhunter_precheck.py")
|
||||
JOB_ID = "coinhunter-trade"
|
||||
|
||||
with open(LOCK_PATH, "w") as f:
|
||||
try:
|
||||
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError:
|
||||
sys.exit(0) # another instance is running
|
||||
|
||||
result = json.loads(os.popen(f"python {PRECHECK}").read())
|
||||
if result.get("should_analyze"):
|
||||
# Trigger Hermes cron only if not already requested
|
||||
state_path = os.path.expanduser("~/.coinhunter/state/precheck_state.json")
|
||||
state = json.load(open(state_path))
|
||||
if not state.get("run_requested_at"):
|
||||
subprocess.run(["hermes", "cron", "run", JOB_ID], check=False)
|
||||
```
|
||||
|
||||
System crontab entry:
|
||||
```cron
|
||||
*/5 * * * * /usr/bin/python3 /home/user/.hermes/scripts/coinhunter_external_gate.py >> /home/user/.coinhunter/logs/external_gate.log 2>&1
|
||||
```
|
||||
|
||||
With this setup, the model is only invoked when a material market change occurs—preserving intelligence while cutting routine cost by 80-95%.
|
||||
|
||||
### Production hardening (mandatory)
|
||||
The live trading stack must include these safeguards:
|
||||
1. **Idempotency** — every decision carries a `decision_id`. The executor checks `~/.coinhunter/executions.json` before submitting orders to prevent duplicate trades.
|
||||
|
||||
Reference in New Issue
Block a user