Files
Hermes-Skills/okx-auto-position/references/signal-staleness-pipeline.md
T

120 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Signal Staleness Pipeline (v4.4.0, 2026-07-08)
End-to-end flow that auto-filters TG trading signals older than 30 minutes.
## Why
Signals arrive via TelegramForwarder in the "交易信号" group. The TG signal source
itself does not embed a timestamp in the message body — it just publishes structured
【币种】【方向】... blocks. Without intervention, `process_signal.py` treats every
forwarded message as fresh, even if the source posted it hours ago and the price has
since moved 3%.
## The chain
```
[signal source @ TG]
│ posts message at T₀ (real wall-clock time)
[forwarder `is_original_time=1, time_template='⏱信号时间: {time}'`]
│ reads `event.message.date` (UTC, tz-aware)
│ appends "\n\n⏱信号时间: 2026-07-08 17:30:00" to forwarded text
[process_signal.py `parse_signal()`]
│ regex: ⏱信号时间[:]\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})
│ parses → fields['signal_time'] = datetime(...)
[is_signal_stale() in process_signal.py]
│ if (now signal_time) >= 30 min → STALE
[format_stale_message()] → push to QQ → return early
│ (no advisor call, no execute, no signal_tracker write of consequence)
```
## Configuration touch-points
### Forwarder DB (rebuilt from container DB)
```python
import sqlite3
conn = sqlite3.connect('/home/openclaw/TelegramForwarder/db/forward.db')
conn.execute("""
UPDATE forward_rules
SET is_original_time = 1,
time_template = '⏱信号时间: {time}'
WHERE id IN (1, 2)
""")
conn.commit()
```
After updating, restart the forwarder container:
```bash
docker restart telegram-forwarder
```
Verify the columns exist (they do in Heavrnl/TelegramForwarder's schema):
```python
import sqlite3
conn = sqlite3.connect('/home/openclaw/TelegramForwarder/db/forward.db')
cols = [r[1] for r in conn.execute('PRAGMA table_info(forward_rules)')]
assert 'is_original_time' in cols and 'time_template' in cols
```
InfoFilter implementation that reads these columns lives at
`/app/filters/info_filter.py` inside the container.
### process_signal.py
```python
SIGNAL_FRESH_MINUTES = 30 # ≥30 算过期(少误跟)
def is_signal_stale(fields):
st = fields.get('signal_time')
if not st:
return False # 无时间戳的旧信号源按新鲜处理
return (datetime.now() - st).total_seconds() >= SIGNAL_FRESH_MINUTES * 60
```
## Edge cases observed
| Case | Behavior |
|---|---|
| Signal has no `⏱信号时间` (older source, forwarded differently) | Treated as fresh — does NOT block. Intentional, so old sources remain routable. |
| Timestamp at exactly 30 min mark | Stale (boundary = ≥, not >). Avoids race on the threshold. |
| Forwarder not yet restarted after DB update | Forwarded messages lack the timestamp → goes through normally. |
| System clock skew between forwarder host and agent | Drift shows up as age_delta. NTP drift is small enough that 30 min is robust. If drift ever causes false STALE → bump `SIGNAL_FRESH_MINUTES` to 45. |
## Rolling back
```sql
UPDATE forward_rules SET is_original_time = 0; -- DB rollback
```
Then in `process_signal.py`, delete the stale-check branch and the
`format_stale_message` helper.
## Testing without real signals
```bash
python3 -c "
from datetime import datetime, timedelta
import sys; sys.path.insert(0, '$HOME/.hermes/skills/trading/okx-auto-position/scripts')
from process_signal import parse_signal, is_signal_stale
for m in [0, 29, 30, 45, 120]:
t = (datetime.now() - timedelta(minutes=m)).strftime('%Y-%m-%d %H:%M:%S')
f = parse_signal(f'【X】⏱信号时间: {t} 【币种】: BTC 【方向】: 做多')
print(f'{m:>3}min → stale={is_signal_stale(f)}')
"
```
Expected: `False False True True True`.
## Why this is not a cron/script-only concern
The staleness check belongs to `process_signal.py` itself, NOT to a cron
wrapper, because the agent (`/skill_name open-position` etc.) can also run
`process_signal.py` directly via terminal — that path also needs the guard.