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.
|
- 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.
|
- 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)
|
### Production hardening (mandatory)
|
||||||
The live trading stack must include these safeguards:
|
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.
|
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