Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ curl -fsSL https://raw.githubusercontent.com/tedboudros/ClawQuant/main/install.s

- **Conversational AI via Telegram + Discord**: one AI interface handles chat, trade confirmations, portfolio queries, and task management.
- **Multi-step tool loops**: the model can chain multiple tool calls in one turn before replying.
- **Live signal lifecycle for AI-proposed positions**: `open_potential_position` runs synchronous risk gating and then delivery (`signal.proposed -> signal.approved/rejected -> signal.delivered`).
- **Signal-ID confirmation tracking**: delivered signals require `confirm_signal(signal_id, entry_price, quantity)` or `skip_signal(signal_id, reason?)`, with one timeout reminder and pending state preserved.
- **Task scheduling with AI self-invocation**: `ai.run_prompt` lets scheduled jobs run through the same central AI stack.
- **Built-in schedulers/handlers**: `ai.run_prompt`, `news.briefing`, `notifications.send`, `comparison.weekly`.
- **Plugin-defined AI tools**: plugins can register tools dynamically (`get_tools` / `call_tool`).
Expand All @@ -31,7 +33,7 @@ curl -fsSL https://raw.githubusercontent.com/tedboudros/ClawQuant/main/install.s

## Coming Soon (Documented Target-State, Not Fully Wired Yet)

- Full autonomous **orchestrator -> risk gate -> signal delivery** production pipeline.
- Full autonomous **orchestrator-driven** production pipeline from ambient live inputs (without explicit AI tool invocation).
- Additional integrations described in docs/examples (email/webhook/custom scrapers).
- Additional market-data providers described in docs/examples (e.g., CoinGecko).
- Auto-created default recurring learning tasks from config at startup.
Expand All @@ -41,10 +43,11 @@ curl -fsSL https://raw.githubusercontent.com/tedboudros/ClawQuant/main/install.s

1. **Message arrives** via Telegram/Discord plugin.
2. **AI interface runs** with tool-calling (including plugin tools).
3. **Tools execute** actions (record trades, manage tasks, fetch prices/news/web results).
4. **Response is published** on `integration.output`.
5. **Output dispatcher delivers** via the right adapter/channel.
6. **Scheduler runs tasks** and can invoke the same AI (`ai.run_prompt`).
3. **Tools execute** actions (including proposing signals, recording confirmations, managing tasks, and fetching prices/news/web results).
4. **Signal proposals** (when used) pass through deterministic risk gating and adapter delivery.
5. **Responses/notifications are published** on `integration.output`.
6. **Output dispatcher delivers** via the right adapter/channel.
7. **Scheduler runs tasks** and can invoke the same AI (`ai.run_prompt`).

## Design

Expand Down
26 changes: 26 additions & 0 deletions core/duration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Duration parsing helpers for configuration values."""

from __future__ import annotations

import re
from datetime import timedelta

_DURATION_RE = re.compile(r"^\s*(\d+)\s*([smhd])\s*$", re.IGNORECASE)
_UNIT_SECONDS = {
"s": 1,
"m": 60,
"h": 3600,
"d": 86400,
}


def parse_duration(value: str) -> timedelta:
"""Parse compact duration strings like '60s', '4h', '7d'."""
match = _DURATION_RE.match(str(value or ""))
if not match:
raise ValueError(f"Invalid duration: {value!r}. Expected '<int><s|m|h|d>'.")

amount = int(match.group(1))
unit = match.group(2).lower()
seconds = amount * _UNIT_SECONDS[unit]
return timedelta(seconds=seconds)
4 changes: 4 additions & 0 deletions core/models/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class Signal(BaseModel):
risk_result: RiskResult | None = None
delivered_at: datetime | None = None
delivered_via: str | None = None
confirmation_status: Literal["pending", "confirmed", "skipped"] | None = None
confirmation_due_at: datetime | None = None
confirmation_reminder_sent_at: datetime | None = None
delivery_errors: list[str] | None = None


class Position(BaseModel):
Expand Down
25 changes: 20 additions & 5 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ This file separates:
Current runtime is **chat-first**:
`integration message -> AI tool loop -> integration.output -> output dispatcher`.

Signal lifecycle is also live for AI-proposed positions:
`open_potential_position -> signal.proposed -> risk gate -> signal.approved/rejected -> signal.delivered`.

---

## Core Design Principles
Expand Down Expand Up @@ -48,6 +51,9 @@ graph TB
subgraph Core
AI[AI Interface]
BUS[AsyncIOBus]
RISK[RiskEngine]
DEL[SignalDeliveryService]
REM[PendingConfirmationWatcher]
SCH[Scheduler]
OUT[OutputDispatcher]
REG[PluginRegistry]
Expand All @@ -65,6 +71,9 @@ graph TB
AI --> BT
AI --> PT
AI --> BUS
BUS --> RISK
BUS --> DEL
REM --> BUS
SCH --> TH
TH --> BUS
BUS --> OUT
Expand All @@ -84,6 +93,9 @@ graph TB
- `Scheduler`
- `PortfolioTracker`
- `AIInterface`
- `RiskEngine` subscriber (`signal.proposed`)
- `SignalDeliveryService` subscriber (`signal.approved`)
- `PendingConfirmationWatcher` loop (overdue pending reminders)
- `OutputDispatcher` subscribed to `integration.output`
- `aiohttp` server (`server.py`)

Expand All @@ -100,7 +112,7 @@ graph TB
- `web.search`
- `browser.selenium` (optional, loaded only when `selenium_browser` is enabled)

Risk rules are loaded and registered, but risk-gating events are part of the target-state pipeline (see below).
Risk rules are loaded and actively evaluated in runtime through `RiskEngine`.

---

Expand All @@ -110,8 +122,9 @@ Risk rules are loaded and registered, but risk-gating events are part of the tar

### Built-in tools

- `confirm_trade`
- `skip_trade`
- `open_potential_position`
- `confirm_signal`
- `skip_signal`
- `close_position`
- `user_initiated_trade`
- `get_portfolio`
Expand Down Expand Up @@ -177,6 +190,7 @@ Scheduled run context order:
3. task prompt (`params.prompt`)

Result is published as `integration.output` and delivered through the same output dispatcher path as chat.
If the scheduled AI responds with exactly `[NO_REPLY]`, delivery is skipped for that run.

---

Expand All @@ -191,6 +205,8 @@ All outbound user-visible messages are published as `integration.output` events.

This keeps integrations modular and transport-focused.

For approved trade signals, `SignalDeliveryService` uses output adapters' `send(signal, memo=None)` path and then emits `signal.delivered` when at least one adapter succeeds.

---

## HTTP Server (Current Routes)
Expand Down Expand Up @@ -238,8 +254,7 @@ This keeps integrations modular and transport-focused.
The following modules and flows exist conceptually (and partly in code) but are **not the default live runtime path** today:

- `engine/orchestrator.py` as always-on subscriber for live input events
- `risk/engine.py` active gating of `signal.proposed` events
- Full signal lifecycle chain in production (`signal.proposed` -> `signal.approved/rejected` -> `signal.delivered`)
- Fully autonomous ambient orchestrator triggering (without explicit tool invocation)
- Auto-creation of recurring tasks from config (`scheduler.default_tasks`, `learning.comparison_schedule`)
- Simulator exposure through CLI/server with validated workflow coverage
- Additional integrations/providers shown in legacy examples (email/webhook/custom scraper/CoinGecko)
16 changes: 10 additions & 6 deletions docs/DATA_MODELS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ class Event(BaseModel):
| `memory.created` | Yes | Yes (when comparison creates memory) |
| `context.assembled` | Yes | Target-state path |
| `memo.created` | Yes | Target-state path |
| `signal.proposed` | Yes | Target-state path |
| `signal.approved` | Yes | Target-state path |
| `signal.rejected` | Yes | Target-state path |
| `signal.delivered` | Yes | Target-state path |
| `alert.triggered` | Yes | Target-state path |
| `signal.proposed` | Yes | Yes (via `open_potential_position`) |
| `signal.approved` | Yes | Yes |
| `signal.rejected` | Yes | Yes |
| `signal.delivered` | Yes | Yes |
| `alert.triggered` | Yes | Yes (delivery failures) |
| `simulation.started/completed` | Yes | Not emitted by current simulation module |

---
Expand Down Expand Up @@ -116,9 +116,13 @@ class Signal(BaseModel):
risk_result: RiskResult | None
delivered_at: datetime | None
delivered_via: str | None
confirmation_status: Literal["pending", "confirmed", "skipped"] | None
confirmation_due_at: datetime | None
confirmation_reminder_sent_at: datetime | None
delivery_errors: list[str] | None
```

Status fields are valid and persisted; however, in default live runtime the full orchestrator/risk delivery lifecycle is not the primary wired path.
Signal lifecycle fields are actively used in runtime for tool-driven proposals and confirmation tracking.

---

Expand Down
46 changes: 45 additions & 1 deletion docs/FLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,49 @@ Key behaviors:

---

## Current Runtime Flow: AI-Proposed Signal (`open_potential_position`)

1. AI calls `open_potential_position` with ticker/direction/catalyst/confidence/entry target/horizon.
2. Interface persists a proposed signal and publishes `signal.proposed`.
3. `RiskEngine` evaluates all registered risk rules synchronously.
4. If rejected:
- signal persisted as `rejected`
- tool result includes failed rule details so AI can retry with adjusted parameters.
5. If approved:
- AI portfolio position is opened
- signal persisted as `approved`
- `signal.approved` published.
6. `SignalDeliveryService` broadcasts the signal via all configured output adapters.
7. If any adapter succeeds:
- signal persisted as `delivered`
- confirmation status set to `pending`
- confirmation due timestamp set from `position_tracking.confirmation_timeout`
- `signal.delivered` published.
8. If all adapters fail:
- signal remains `approved`
- delivery errors are persisted
- `alert.triggered` published.

---

## Current Runtime Flow: Signal Confirmation (`confirm_signal` / `skip_signal`)

1. AI references signal IDs from `get_signals` context and calls `confirm_signal` or `skip_signal`.
2. Interface validates:
- signal exists
- signal is `delivered`
- signal confirmation is `pending`
- for confirm: `entry_price > 0` and `quantity > 0`.
3. On confirm:
- human portfolio position is recorded with `signal_id`
- signal confirmation status becomes `confirmed`.
4. On skip:
- skipped human position is recorded with `signal_id`
- signal confirmation status becomes `skipped`.
5. Pending confirmations past due receive one reminder message and remain pending.

---

## Current Runtime Flow: Create Scheduled AI Task (`ai.run_prompt`)

1. User asks to schedule recurring monitoring.
Expand All @@ -57,6 +100,7 @@ Key behaviors:
5. Handler `ai.run_prompt` runs same central AI stack.
6. Scheduled run injects: `system prompt -> last 10 channel messages -> task prompt`.
7. Result is published to `integration.output` and delivered back to same channel.
If the AI returns exactly `[NO_REPLY]`, delivery is skipped for that run.

---

Expand Down Expand Up @@ -96,7 +140,7 @@ Key behaviors:

The following are documented architecture goals but are **not fully wired as the default live runtime path**:

- Live input -> orchestrator agent chain -> `signal.proposed` -> risk gate -> delivery.
- Live input -> orchestrator agent chain -> autonomous signal proposal without explicit tool invocation.
- Email/webhook/custom scraper as first-class live integrations.
- Automatic recurring learning/comparison task creation from config on startup.
- `run_analysis` event path consumed by an active orchestrator subscriber in production runtime.
Expand Down
12 changes: 8 additions & 4 deletions docs/LEARNING_LOOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ This doc describes what is implemented today and what is still target-state.
- Dual portfolio files:
- `positions/ai/*.json`
- `positions/human/*.json`
- Signal confirmation lifecycle:
- `confirm_signal(signal_id, entry_price, quantity)`
- `skip_signal(signal_id, reason?)`
- one-time pending reminder after `position_tracking.confirmation_timeout`
- Comparison task handler: `comparison.weekly`
- Memory generation model + storage:
- JSON files in `memories/`
Expand All @@ -20,7 +24,7 @@ This doc describes what is implemented today and what is still target-state.
### Not fully wired / coming soon

- Automatic scheduling from `learning.comparison_schedule`
- Automatic assumed-execution timeout flow from `position_tracking.confirmation_timeout`
- Automatic assumed-execution timeout flow (timeout currently reminders-only, remains pending)
- Broker sync path for human portfolio
- Full live orchestrator context-pack loop that routinely feeds memories into every production decision

Expand All @@ -32,14 +36,14 @@ This doc describes what is implemented today and what is still target-state.

- Stored in `~/.clawquant/positions/ai/`
- Intended to represent system-side execution tracking
- In the default chat-first runtime, this portfolio is mainly updated where risk/orchestrator flows are explicitly invoked (target-state path), not by everyday chat tasking alone
- In the default chat-first runtime, this portfolio is updated when AI uses `open_potential_position` and signals pass risk approval

### Human Portfolio (actual)

- Stored in `~/.clawquant/positions/human/`
- Updated directly by AI tools such as:
- `confirm_trade`
- `skip_trade`
- `confirm_signal`
- `skip_signal`
- `close_position`
- `user_initiated_trade`

Expand Down
Loading