| name | trading-review |
| description | Weekly self-improvement review for the S&P 500 trading system. Reads trade ledger and metrics, compares to goals, diagnoses weaknesses, proposes one-variable parameter mutation OR writes a new signal plugin if a fundamentally new signal is needed. Includes full signal evolution capability. |
| version | 1.0.0 |
| metadata | {"hermes":{"tags":["trading","finance","self-improvement","stocks"],"category":"finance","requires_toolsets":["terminal","files"]}} |
Trading Review Skill
When to Use
Run this skill every week (or manually) to review the paper trading system's performance
and propose exactly one parameter improvement.
Trigger with: /skills trading-review
Inputs
Read these files in this order before writing anything:
| File | Purpose |
|---|
hermes/TRADING_MEMORY.md | Read first โ your persistent memory of past experiments and insights |
data/trades.jsonl | Full trade ledger (one JSON per line) |
state/baseline_metrics.json | Last confirmed baseline performance |
state/active_experiment.yaml | Current experiment in progress (if any) |
state/learned_parameters.json | Current tunable parameters (your domain) |
state/filter_parameters.json | Cornelius's parameters โ read only, do not write |
config/goals.yaml | Success/failure thresholds |
config/strategy.yaml | Strategy description |
Also search your past Hermes sessions for any prior trading-related conversations to surface forgotten insights before proposing a new hypothesis.
Procedure
Step 1 โ Load and validate data
- Read
data/trades.jsonl. Count total trades, open trades, and closed trades.
- Confirm at least 10 closed trades exist. If fewer, write a review noting insufficient data and stop.
- Check for gaps: are there missing dates that suggest the runner failed?
Step 2 โ Compute or verify metrics
Compute from closed trades:
- Win rate: % of closed trades with realized_pnl > 0
- Average PnL per trade: mean of realized_pnl
- Max drawdown: largest peak-to-trough loss in cumulative PnL
- Rolling Sharpe (30d): annualized Sharpe over last 30 days of trades
- Sortino ratio: downside-adjusted Sharpe
You can run the built-in metrics by executing:
python -c "from src.metrics.metrics import compute_all; import json; print(json.dumps(compute_all(), indent=2))"
Step 3 โ Compare to goals
Read config/goals.yaml. Check each success threshold:
min_rolling_sharpe โ is current Sharpe above this?
min_annual_return_pct โ on track?
max_drawdown_pct โ within limit?
min_trade_count โ enough trades?
Check each failure threshold:
max_drawdown_pct
consecutive_losing_weeks
min_rolling_sharpe
Classify current state as: on_track, underperforming, or failing.
Step 4 โ Check active experiment
Read state/active_experiment.yaml.
If status: running:
- Compare current Sharpe vs
baseline_sharpe
- If improvement >=
success_criteria.min_sharpe_improvement AND evaluation period elapsed:
- Mark experiment as completed โ promote the new parameter value to baseline
- Update
state/learned_parameters.json with the new value
- Update
state/baseline_metrics.json
- If performance worse than
rollback_rule thresholds:
- Mark experiment as reverted โ restore old value in
state/learned_parameters.json
- If still within evaluation window: note experiment is ongoing
If status: none or status: completed or status: reverted:
- Proceed to Step 5 (propose new experiment)
Step 4b โ Coordination check with Cornelius
Read hermes/TRADING_MEMORY.md Cornelius Coordination Log. Do not propose a variable that Cornelius changed in the last 7 days. Your domains are separate but both affect Sharpe โ conflicting simultaneous experiments invalidate both results.
Step 5 โ Diagnose the primary weakness
Identify the single biggest issue from these candidates:
| Symptom | Possible cause | Variable to tune |
|---|
| Low Sharpe, high volatility | Too many positions / too diversified | n_positions |
| Underperforming in trending market | Momentum weight too low | momentum_weight |
| Poor yield income | Yield weight too low | yield_weight |
| High drawdown | Volatility weight too low | volatility_weight |
| Stale signals | Lookback too long or short | momentum_lookback_months |
| Churn / high turnover | Rebalancing too frequently | rebalance_frequency |
Pick exactly one variable to change. State your hypothesis explicitly.
Step 6 โ Propose the experiment
Fill in state/active_experiment.yaml:
status: running
experiment_id: "<ISO-date>-<variable-name>"
started: "<ISO datetime>"
ends: "<ISO datetime 7 days later>"
hypothesis: "<one sentence explaining why this change should help>"
variable:
name: "<parameter name>"
old_value: <current value>
new_value: <proposed value>
baseline_sharpe: <current rolling_sharpe_30d>
current_sharpe: null
success_criteria:
min_sharpe_improvement: 0.1
min_evaluation_days: 7
rollback_rule:
trigger_if_sharpe_below: <baseline_sharpe - 0.2>
trigger_if_drawdown_exceeds_pct: <current_max_drawdown + 5>
Then update state/learned_parameters.json with the new value.
Update the _meta section:
{
"_meta": {
"last_updated": "<ISO datetime>",
"updated_by": "hermes-trading-review",
"experiment_id": "<experiment_id>"
}
}
Step 7 โ Update TRADING_MEMORY.md
Before writing the review file, append to hermes/TRADING_MEMORY.md:
- Experiment History table โ add a row for any experiment you evaluated this cycle (outcome: promoted / reverted / ongoing)
- Hypothesis Graveyard โ if the experiment was reverted, add the hypothesis and why it failed
- Confirmed Insights โ if the experiment was promoted, add what you learned and why it worked
- Market Regime Notes โ if you observed anything about current market conditions (e.g. "momentum underperforming in choppy market, week of YYYY-MM-DD")
This is your memory. Future-you will read it before proposing a new hypothesis.
Step 8 โ Write the review file
Write to reviews/YYYY-MM-DD-hermes-review.md using the template below.
Do not modify: data/trades.jsonl, config/goals.yaml, config/risk_limits.yaml, .env.
Review Output Template
# Hermes Weekly Strategy Review โ YYYY-MM-DD
## 1. Data Coverage
- Total closed trades: N
- Date range: start โ end
- Data quality issues: none | [describe]
## 2. Current Performance
| Metric | Value | Goal | Status |
|--------|-------|------|--------|
| Rolling Sharpe (30d) | | โฅ 1.0 | โ
/ โ |
| Annual Return % | | โฅ 10% | โ
/ โ |
| Max Drawdown % | | โค 15% | โ
/ โ |
| Win Rate % | | โ | โ |
| Sortino Ratio | | โ | โ |
## 3. Overall State
[on_track | underperforming | failing]
## 4. Active Experiment (if any)
- Experiment: [experiment_id]
- Outcome: [ongoing | promoted | reverted]
- Result: [describe Sharpe change]
## 5. Diagnosis
**Primary weakness:** [describe]
**Evidence:** [specific numbers]
**Alternative hypotheses considered:** [list]
## 6. Proposed Experiment
- **Variable:** [name]
- **Old value:** [value]
- **New value:** [value]
- **Hypothesis:** [one sentence]
- **Expected effect:** [specific metric improvement]
- **Evaluation period:** 7 days
- **Rollback trigger:** Sharpe drops below [value] or drawdown exceeds [value]%
## 7. Actions Taken
- [ ] `state/active_experiment.yaml` updated
- [ ] `state/learned_parameters.json` updated
- [ ] This review written to `reviews/`
## 8. Next Review
[Date 7 days from now]
Signal Evolution Protocol
Use this when parameter tuning has converged (Sharpe not improving after 3+ cycles) and
you believe a new signal is needed โ not just a weight adjustment.
When to evolve vs when to tune
| Situation | Action |
|---|
| Sharpe improving each cycle | Continue parameter tuning |
| Sharpe plateaued for 3+ cycles, weights already explored | Consider a new signal |
| A specific market pattern is clearly not captured | Write a new signal |
| Parameter tuning space exhausted | Write a new signal |
Step E1 โ Identify the missing signal
From the trade ledger, identify a pattern the current signals miss. Examples:
- Stocks with recent earnings beats outperform โ add
earnings_momentum
- Stocks below 200-day MA underperform โ add
trend_filter
- High short interest stocks underperform โ add
short_interest_penalty
- Low RSI stocks mean-revert โ add
rsi_mean_reversion
Step E2 โ Write the signal file
Create src/strategy/signals/<signal_name>.py. The file must:
- Import
Signal from src.strategy.signals.base
- Define exactly one class inheriting
Signal
- Set
name = "<signal_name>" matching the filename exactly
- Set
description = "<one sentence>"
- Implement
compute(self, prices, **kwargs) -> pd.Series
prices is a DataFrame[date ร ticker] of adjusted closes
- Return raw float scores โ higher = more attractive
- Do NOT rank/normalise โ the registry does that
- Use only
prices and kwargs โ do not fetch data inside compute()
import pandas as pd
from src.strategy.signals.base import Signal
class RSIMeanReversionSignal(Signal):
name = "rsi_mean_reversion"
description = "14-day RSI โ low RSI stocks score higher (mean reversion)"
def compute(self, prices: pd.DataFrame, rsi_period: int = 14, **kwargs) -> pd.Series:
delta = prices.diff()
gain = delta.clip(lower=0).rolling(rsi_period).mean()
loss = (-delta.clip(upper=0)).rolling(rsi_period).mean()
rs = gain / loss.replace(0, float("nan"))
rsi = 100 - (100 / (1 + rs))
return (100 - rsi.iloc[-1]).rename("rsi_mean_reversion")
Step E3 โ Write the test
Create tests/signals/test_<signal_name>.py:
import pandas as pd
import numpy as np
import pytest
from src.strategy.signals.<signal_name> import <ClassName>
def _prices(n=20, days=300):
np.random.seed(42)
tickers = [f"T{i:03d}" for i in range(n)]
dates = pd.date_range("2024-01-01", periods=days, freq="B")
return 100 * (1 + pd.DataFrame(
np.random.normal(0.0005, 0.01, (days, n)),
index=dates, columns=tickers,
)).cumprod()
def test_returns_series():
result = <ClassName>().compute(_prices())
assert isinstance(result, pd.Series)
def test_returns_floats():
result = <ClassName>().compute(_prices())
assert result.dtype.kind == "f"
def test_no_all_nan():
result = <ClassName>().compute(_prices())
assert result.notna().any()
def test_nonzero_spread():
result = <ClassName>().compute(_prices()).dropna()
assert result.max() - result.min() > 0
Step E4 โ Run tests (MANDATORY)
python -m pytest tests/signals/ -v
If any test fails: delete the signal file. Do not activate it. Log the failure in TRADING_MEMORY.md and try a different approach.
If all tests pass, continue.
Step E5 โ Activate the signal
Update state/signal_registry.json:
{
"active_signals": ["momentum", "dividend_yield", "volatility_penalty", "<signal_name>"],
"weights": {
"momentum": 0.5,
"dividend_yield": 0.3,
"volatility_penalty": 0.2,
"<signal_name>": 0.1
},
"signal_params": {
"<signal_name>": { "param_name": "value" }
}
}
Start with a small weight (0.1). Treat this as a one-variable experiment in state/active_experiment.yaml. The variable being changed is "added signal <signal_name> at weight 0.1."
Step E6 โ Update TRADING_MEMORY.md
Add to Experiment History:
| [date] | new_signal: <signal_name> | โ | weight 0.1 | running | โ | <hypothesis> |
Pitfalls
- Do not change more than one variable per cycle.
- Do not override
config/goals.yaml โ only the parameters in state/learned_parameters.json are yours to modify.
- If you cannot compute Sharpe (too few trades), write the review with "INSUFFICIENT_DATA" and do not propose an experiment.
- Do not invent trades or fabricate metrics โ read the ledger directly.
- If the mode is
read_only, do not write to learned_parameters.json โ note the proposed change in the review only.
Verification
After writing:
- Confirm
reviews/YYYY-MM-DD-hermes-review.md exists
- Confirm
state/active_experiment.yaml has status: running (or a valid reason it does not)
- Confirm
state/learned_parameters.json has updated _meta.last_updated
- Run
python -c "from src.metrics.metrics import compute_all; print('OK')" to verify the runner still imports cleanly