v4.5.38: 实时数据查询硬规则(回复前必查)+ check_account.py封装
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user