solana-traderclaw 1.0.92 → 1.0.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1171,9 +1171,31 @@ var solanaTraderPlugin = {
1171
1171
  if (typeof val === "object") return JSON.stringify(val);
1172
1172
  return String(val);
1173
1173
  };
1174
+ const getNestedValue = (obj, key) => {
1175
+ const nested = key.split(".");
1176
+ let cur = obj;
1177
+ for (const k of nested) {
1178
+ if (!cur || typeof cur !== "object") return void 0;
1179
+ cur = cur[k];
1180
+ }
1181
+ return cur;
1182
+ };
1183
+ const resolveStrategyField = (state, field) => {
1184
+ return getNestedValue(state, `strategy.${field}`) ?? state[field];
1185
+ };
1186
+ const renderKeyValueSection = (obj, indent = "") => {
1187
+ if (!obj || typeof obj !== "object") return [];
1188
+ const lines = [];
1189
+ for (const [k, v] of Object.entries(obj)) {
1190
+ if (v !== void 0 && v !== null) {
1191
+ lines.push(`${indent}- ${k}: ${typeof v === "object" ? JSON.stringify(v) : String(v)}`);
1192
+ }
1193
+ }
1194
+ return lines;
1195
+ };
1174
1196
  const generateMemoryMd = (aid, stateObj) => {
1175
1197
  const lines = [
1176
- `# ${aid} \u2014 Durable Memory`,
1198
+ `# MEMORY.md`,
1177
1199
  ``,
1178
1200
  `> Auto-generated by solana_state_save. OpenClaw loads this file into context at every session start.`,
1179
1201
  `> Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -1184,52 +1206,175 @@ var solanaTraderPlugin = {
1184
1206
  return lines.join("\n");
1185
1207
  }
1186
1208
  const state = stateObj;
1187
- const identity = [];
1188
- if (state.tier) identity.push(`- **Tier:** ${state.tier}`);
1189
- if (state.walletId) identity.push(`- **Wallet:** ${state.walletId}`);
1190
- if (state.mode) identity.push(`- **Mode:** ${state.mode}`);
1191
- if (state.strategyVersion) identity.push(`- **Strategy Version:** ${state.strategyVersion}`);
1192
- if (state.regime) identity.push(`- **Regime:** ${formatStateValue(state.regime)}`);
1193
- if (state.maxPositions) identity.push(`- **Max Positions:** ${state.maxPositions}`);
1194
- if (state.maxPositionSizeSol) identity.push(`- **Max Position Size:** ${state.maxPositionSizeSol} SOL`);
1195
- if (identity.length > 0) {
1196
- lines.push("## Identity & Config", "", ...identity, "");
1209
+ const identityFields = [
1210
+ ["tier", "Tier"],
1211
+ ["walletId", "Wallet"],
1212
+ ["mode", "Mode"],
1213
+ ["strategyVersion", "Strategy Version"],
1214
+ ["regime", "Regime"],
1215
+ ["maxPositions", "Max Positions"],
1216
+ ["minBuy", "Min Buy"],
1217
+ ["minTp", "Min TP"]
1218
+ ];
1219
+ const identityLines = [];
1220
+ for (const [key, label] of identityFields) {
1221
+ const val = state[key];
1222
+ if (val !== void 0 && val !== null) {
1223
+ identityLines.push(`- ${label}: ${formatStateValue(val)}`);
1224
+ }
1197
1225
  }
1198
- if (state.defenseMode !== void 0) lines.push(`## Defense Mode
1199
-
1200
- - **Active:** ${state.defenseMode}
1201
- `);
1202
- if (state.killSwitchActive !== void 0) lines.push(`## Kill Switch
1203
-
1204
- - **Active:** ${state.killSwitchActive}
1205
- `);
1206
- if (state.watchlist && Array.isArray(state.watchlist) && state.watchlist.length > 0) {
1207
- lines.push("## Watchlist", "");
1208
- for (const item of state.watchlist.slice(0, 20)) {
1209
- lines.push(`- ${typeof item === "string" ? item : JSON.stringify(item)}`);
1226
+ if (state.maxPositionSizeSol) identityLines.push(`- Max Position Size: ${state.maxPositionSizeSol} SOL`);
1227
+ const tpStructure = state.tpStructure;
1228
+ if (tpStructure && typeof tpStructure === "object") {
1229
+ identityLines.push("- TP Structure:");
1230
+ for (const line of renderKeyValueSection(tpStructure, " ")) {
1231
+ identityLines.push(line);
1210
1232
  }
1211
- lines.push("");
1233
+ }
1234
+ if (identityLines.length > 0) {
1235
+ lines.push("## Trading Identity", "", ...identityLines, "");
1236
+ }
1237
+ if (state.defenseMode !== void 0) lines.push(`## Defense Mode`, "", `- Active: ${state.defenseMode}`, "");
1238
+ if (state.killSwitchActive !== void 0) lines.push(`## Kill Switch`, "", `- Active: ${state.killSwitchActive}`, "");
1239
+ if (identityLines.length > 0 || state.defenseMode !== void 0 || state.killSwitchActive !== void 0) {
1240
+ lines.push("---", "");
1241
+ }
1242
+ const strategyHasContent = ["featureWeights", "positionSizing", "stopLoss", "takeProfit", "entryFilter", "deadMoney"].some((f) => resolveStrategyField(state, f) !== void 0);
1243
+ if (strategyHasContent) {
1244
+ lines.push("## STRATEGY", "");
1245
+ const featureWeights = resolveStrategyField(state, "featureWeights");
1246
+ if (featureWeights && typeof featureWeights === "object") {
1247
+ lines.push("### Feature Weights", "");
1248
+ lines.push(...renderKeyValueSection(featureWeights));
1249
+ lines.push("");
1250
+ }
1251
+ const positionSizing = resolveStrategyField(state, "positionSizing");
1252
+ if (positionSizing && typeof positionSizing === "object") {
1253
+ lines.push("### Position Sizing Rules", "");
1254
+ lines.push(...renderKeyValueSection(positionSizing));
1255
+ lines.push("");
1256
+ }
1257
+ const stopLoss = resolveStrategyField(state, "stopLoss");
1258
+ if (stopLoss && typeof stopLoss === "object") {
1259
+ lines.push("### Stop-Loss Rules", "");
1260
+ lines.push(...renderKeyValueSection(stopLoss));
1261
+ lines.push("");
1262
+ }
1263
+ const takeProfit = resolveStrategyField(state, "takeProfit");
1264
+ if (takeProfit && typeof takeProfit === "object") {
1265
+ lines.push("### Take-Profit Rules", "");
1266
+ lines.push(...renderKeyValueSection(takeProfit));
1267
+ lines.push("");
1268
+ }
1269
+ const entryFilter = resolveStrategyField(state, "entryFilter");
1270
+ if (entryFilter && typeof entryFilter === "object") {
1271
+ lines.push("### Entry Filter", "");
1272
+ lines.push(...renderKeyValueSection(entryFilter));
1273
+ lines.push("");
1274
+ }
1275
+ const deadMoney = resolveStrategyField(state, "deadMoney");
1276
+ if (deadMoney && typeof deadMoney === "object") {
1277
+ lines.push("### Dead Money Rule", "");
1278
+ lines.push(...renderKeyValueSection(deadMoney));
1279
+ lines.push("");
1280
+ }
1281
+ lines.push("---", "");
1212
1282
  }
1213
1283
  if (state.permanentLearnings && Array.isArray(state.permanentLearnings)) {
1214
1284
  lines.push("## Permanent Learnings", "");
1215
- for (const learning of state.permanentLearnings.slice(0, 30)) {
1216
- lines.push(`- ${typeof learning === "string" ? learning : JSON.stringify(learning)}`);
1285
+ state.permanentLearnings.slice(0, 30).forEach((learning, i) => {
1286
+ lines.push(`${i + 1}. ${typeof learning === "string" ? learning : JSON.stringify(learning)}`);
1287
+ });
1288
+ lines.push("", "---", "");
1289
+ }
1290
+ const capitalStatus = state.capitalStatus;
1291
+ if (capitalStatus && typeof capitalStatus === "object") {
1292
+ const cs = capitalStatus;
1293
+ lines.push("## Capital Status Tracker", "");
1294
+ if (cs.lastUpdated) lines.push(`Last Updated: ${cs.lastUpdated}`);
1295
+ const csFields = [
1296
+ ["walletBalance", "Wallet Balance"],
1297
+ ["realizedPnlAllTime", "Realized P&L (All-Time)"],
1298
+ ["openPositions", "Open Positions"],
1299
+ ["dailyNotionalUsed", "Daily Notional Used"],
1300
+ ["dailyRealizedLoss", "Daily Realized Loss"],
1301
+ ["killSwitch", "Kill Switch"]
1302
+ ];
1303
+ for (const [key, label] of csFields) {
1304
+ if (cs[key] !== void 0) lines.push(`- ${label}: ${formatStateValue(cs[key])}`);
1217
1305
  }
1218
- lines.push("");
1306
+ const renderedCsKeys = /* @__PURE__ */ new Set(["lastUpdated", ...csFields.map(([k]) => k)]);
1307
+ for (const [k, v] of Object.entries(cs)) {
1308
+ if (!renderedCsKeys.has(k) && v !== void 0) lines.push(`- ${k}: ${formatStateValue(v)}`);
1309
+ }
1310
+ lines.push("", "---", "");
1311
+ }
1312
+ if (state.watchlist && Array.isArray(state.watchlist) && state.watchlist.length > 0) {
1313
+ lines.push("## Watchlist", "");
1314
+ for (const item of state.watchlist.slice(0, 20)) {
1315
+ lines.push(`- ${typeof item === "string" ? item : JSON.stringify(item)}`);
1316
+ }
1317
+ lines.push("", "---", "");
1219
1318
  }
1220
1319
  if (state.regimeCanary && typeof state.regimeCanary === "object") {
1221
1320
  const rc = state.regimeCanary;
1222
- lines.push("## Regime Canary", "", `- **Regime:** ${rc.regime || "unknown"}`, `- **Detected At:** ${rc.detectedAt || "unknown"}`, "");
1321
+ lines.push("## Regime Canary", "", `- Regime: ${rc.regime || "unknown"}`, `- Detected At: ${rc.detectedAt || "unknown"}`, "", "---", "");
1223
1322
  }
1224
1323
  if (state.preferences && typeof state.preferences === "object") {
1225
1324
  const prefs = state.preferences;
1226
1325
  const prefLines = [];
1227
1326
  for (const [pk, pv] of Object.entries(prefs)) {
1228
- prefLines.push(`- **${pk}:** ${formatStateValue(pv)}`);
1327
+ prefLines.push(`- ${pk}: ${formatStateValue(pv)}`);
1328
+ }
1329
+ if (prefLines.length > 0) lines.push("## User Preferences (override defaults)", "", ...prefLines, "", "---", "");
1330
+ }
1331
+ const nextCycle = state.nextCycle;
1332
+ if (nextCycle && typeof nextCycle === "object") {
1333
+ const nc = nextCycle;
1334
+ lines.push("## Next Trading Cycle", "");
1335
+ const ncFields = [
1336
+ ["scan", "Scan"],
1337
+ ["candidatesInLab", "Candidates in Lab"],
1338
+ ["strategy", "Strategy"],
1339
+ ["focus", "Focus"],
1340
+ ["action", "Action"]
1341
+ ];
1342
+ for (const [key, label] of ncFields) {
1343
+ if (nc[key] !== void 0) lines.push(`- ${label}: ${formatStateValue(nc[key])}`);
1344
+ }
1345
+ const renderedNcKeys = new Set(ncFields.map(([k]) => k));
1346
+ for (const [k, v] of Object.entries(nc)) {
1347
+ if (!renderedNcKeys.has(k) && v !== void 0) lines.push(`- ${k}: ${formatStateValue(v)}`);
1229
1348
  }
1230
- if (prefLines.length > 0) lines.push("## User Preferences (override defaults)", "", ...prefLines, "");
1349
+ lines.push("");
1231
1350
  }
1232
- const structuredKeys = /* @__PURE__ */ new Set(["tier", "walletId", "mode", "strategyVersion", "regime", "maxPositions", "maxPositionSizeSol", "defenseMode", "killSwitchActive", "watchlist", "permanentLearnings", "regimeCanary", "preferences"]);
1351
+ const structuredKeys = /* @__PURE__ */ new Set([
1352
+ "tier",
1353
+ "walletId",
1354
+ "mode",
1355
+ "strategyVersion",
1356
+ "regime",
1357
+ "maxPositions",
1358
+ "maxPositionSizeSol",
1359
+ "defenseMode",
1360
+ "killSwitchActive",
1361
+ "watchlist",
1362
+ "permanentLearnings",
1363
+ "regimeCanary",
1364
+ "preferences",
1365
+ "minBuy",
1366
+ "minTp",
1367
+ "tpStructure",
1368
+ "strategy",
1369
+ "featureWeights",
1370
+ "positionSizing",
1371
+ "stopLoss",
1372
+ "takeProfit",
1373
+ "entryFilter",
1374
+ "deadMoney",
1375
+ "capitalStatus",
1376
+ "nextCycle"
1377
+ ]);
1233
1378
  const summaryKeys = /* @__PURE__ */ new Set(["lastCycleSummary"]);
1234
1379
  const otherKeys = Object.keys(state).filter((k) => !structuredKeys.has(k) && !volatileStateKeys.has(k));
1235
1380
  const summaryRendered = [];
@@ -1258,7 +1403,11 @@ var solanaTraderPlugin = {
1258
1403
  const content = generateMemoryMd(aid, stateObj);
1259
1404
  ensureDir(path.dirname(memoryMdPath));
1260
1405
  fs.writeFileSync(memoryMdPath, content, "utf-8");
1261
- } catch {
1406
+ api.logger.info(`[solana-trader] MEMORY.md written successfully (${content.length} bytes) \u2192 ${memoryMdPath}`);
1407
+ return true;
1408
+ } catch (err) {
1409
+ api.logger.error(`[solana-trader] MEMORY.md write FAILED \u2192 path: ${memoryMdPath}, error: ${err instanceof Error ? err.message : String(err)}`);
1410
+ return false;
1262
1411
  }
1263
1412
  };
1264
1413
  const getDailyLogPath = (date) => {
@@ -2779,8 +2928,8 @@ ${notes}
2779
2928
  }
2780
2929
  const payload = { agentId: targetAgentId, state: mergedState, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
2781
2930
  writeJsonFile(filePath, payload);
2782
- writeMemoryMd(targetAgentId, mergedState);
2783
- return { ok: true, agentId: targetAgentId, updatedAt: payload.updatedAt, merged: !shouldOverwrite, memoryMdWritten: true };
2931
+ const mdWritten = writeMemoryMd(targetAgentId, mergedState);
2932
+ return { ok: true, agentId: targetAgentId, updatedAt: payload.updatedAt, merged: !shouldOverwrite, memoryMdWritten: mdWritten, memoryMdPath };
2784
2933
  })
2785
2934
  });
2786
2935
  api.registerTool({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solana-traderclaw",
3
- "version": "1.0.92",
3
+ "version": "1.0.93",
4
4
  "description": "TraderClaw V1-Upgraded — Solana trading for OpenClaw with intelligence lab, tool envelopes, prompt scrubbing, read-only X social intel, and split skill docs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -10,25 +10,25 @@ Read MEMORY.md (auto-loaded). If empty or missing wallet/tier/strategy → run M
10
10
 
11
11
  1. **MEMORY.md** (already in context): tier, wallet, mode, strategy version, watchlist, regime canary
12
12
  2. **Daily log** (`memory/YYYY-MM-DD.md`, auto-loaded): what already happened today — don't repeat work
13
- 3. **Context engine** (automatic): `[TraderClaw Trading Context]` is injected into your system prompt at session start with current state, last 3 decisions, and entitlement limits. You do not need to call anything — just read it when present.
13
+ 3. **Context engine** (automatic): `[TraderClaw Trading Context]` injected into system prompt at session start with current state, last 3 decisions, and entitlement limits. Just read it when present.
14
14
  4. **Server-side memory** — call `solana_memory_search` for: `"source_reputation"`, `"strategy_drift_warning"`, `"pre_trade_rationale"`, `"meta_rotation"`
15
- 5. **QMD recall** — before analyzing any candidate, call `memory_search` with the token symbol or contract address. If you've seen this token before, use prior analysis to: skip repeat work, apply re-entry penalties, catch repeat rug patterns, and reference prior confidence scores.
15
+ 5. **QMD recall** — before analyzing any candidate, call `memory_search` with the token symbol or contract address. If seen before, use prior analysis to: skip repeat work, apply re-entry penalties, catch repeat rug patterns, reference prior confidence scores.
16
16
 
17
17
  ## User Preferences Override (apply before any other step)
18
18
 
19
- If MEMORY.md contains a **User Preferences** section, those values override the defaults in this document for this entire session. No exceptions.
19
+ If MEMORY.md contains a **User Preferences** section, those values override defaults in this document for this entire session.
20
20
 
21
21
  | Preference key | What it overrides |
22
22
  |---|---|
23
- | `volumeMinUsd` | Minimum 24h volume filter in STEP 1 SCAN and alpha_scan cron (default: 50000) |
24
- | `marketCapMinUsd` | Minimum market cap filter (default: 10000) |
25
- | `maxPositionSizeSol` | Maximum position size in SOL (overrides entitlement cap if lower) |
23
+ | `volumeMinUsd` | Min 24h volume filter in STEP 1 SCAN and alpha_scan cron (default: 50000) |
24
+ | `marketCapMinUsd` | Min market cap filter (default: 10000) |
25
+ | `maxPositionSizeSol` | Max position size in SOL (overrides entitlement cap if lower) |
26
26
  | `scanMode` | `"conservative"` / `"standard"` / `"aggressive"` — adjusts confidence thresholds |
27
27
  | `slPct` | Default stop-loss % for new positions (default: 20 HARDENED, 40 DEGEN) |
28
- | `minConfidence` | Minimum confidence score to enter a trade (default: 0.65) |
28
+ | `minConfidence` | Min confidence score to enter a trade (default: 0.65) |
29
29
  | `narrativeFilter` | Comma-separated narrative clusters to focus on (e.g. `"AI,Gaming"`) |
30
30
 
31
- Apply these immediately. Treat them as if the user said them at the start of this session. They are durable they persist until the user explicitly changes them.
31
+ Apply immediately. Durablepersists until user explicitly changes them.
32
32
 
33
33
  ---
34
34
 
@@ -40,23 +40,23 @@ Call `solana_positions`, `solana_killswitch_status`, `solana_capital_status`.
40
40
 
41
41
  **Kill switch active → halt all trading. No exceptions.**
42
42
 
43
- **Deployer reputation check on held positions:** For each open position, call `solana_deployer_trust_get({ address: "<deployer_address>" })`. If a deployer's trust score has dropped significantly since entry (e.g., they launched another token that rugged), flag the position for immediate review.
43
+ **Deployer reputation check:** For each open position, call `solana_deployer_trust_get({ address: "<deployer_address>" })`. If trust score dropped significantly since entry (e.g., they launched another token that rugged), flag for immediate review.
44
44
 
45
- **Dead money check on every open position — apply ALL four criteria:**
45
+ **Dead money check — apply ALL four criteria:**
46
46
  - Loss > 40%
47
47
  - Held 90+ min AND still down 5%+
48
48
  - 24h volume < $5,000
49
49
  - Price flat (±5%) for 4+ hours
50
50
 
51
- If ALL four are true → exit immediately as dead money. Do NOT hold hoping for recovery. MSTR-type 4.75h -97% holds are the #1 capital destroyer. A position at -40% after 90 min with dead volume is NOT coming back.
51
+ If ALL four true → exit immediately. Do NOT hold hoping for recovery. A position at -40% after 90 min with dead volume is NOT coming back.
52
52
 
53
- **Strategy integrity:** Compare your last 3 trade decisions (from memory) against your feature weights. If your actual decisions diverge from what the weights would predict, log `strategy_drift_warning` via `solana_memory_write`.
53
+ **Strategy integrity:** Compare last 3 trade decisions (from memory) against feature weights. If actual decisions diverge from what weights would predict, log `strategy_drift_warning` via `solana_memory_write`.
54
54
 
55
55
  ## STEP 1: SCAN
56
56
 
57
57
  Call `solana_scan_launches` for new launches and `solana_scan_hot_pairs` for hot pairs.
58
58
 
59
- **Bitquery subscription events:** Check `solana_bitquery_subscriptions` for any active streams. Process buffered events from real-time subscriptions (new launches, price alerts, pool changes). If no subscriptions are active and this is the first heartbeat of the session, call `solana_bitquery_templates` to discover available query templates and cache the list in memory.
59
+ **Bitquery subscription events:** Check `solana_bitquery_subscriptions` for active streams. Process buffered events from real-time subscriptions. If no subscriptions active and first heartbeat of session, call `solana_bitquery_templates` to discover available templates and cache in memory.
60
60
 
61
61
  ## STEP 1.5: ALPHA SIGNALS
62
62
 
@@ -67,29 +67,27 @@ Call `solana_alpha_signals` to poll the buffer. Score and classify each signal b
67
67
  solana_source_trust_get({ name: "<signal source>" })
68
68
  solana_alpha_sources()
69
69
  ```
70
- If a source has trust score < 30 or win rate < 25%, downgrade signal priority by one tier. Do NOT skip signals from low-trust sources entirely — still log them — but reduce their weight in your decision.
70
+ If trust score < 30 or win rate < 25%, downgrade signal priority by one tier. Do NOT skip low-trust signals entirely — still log them — but reduce their weight.
71
71
 
72
- **Multi-source conflict detection:** If 2+ signals reference the same token with conflicting `kind` values (e.g., one says `ca_drop` and another says `risk` or `exit`):
72
+ **Multi-source conflict detection:** If 2+ signals reference same token with conflicting `kind` values:
73
73
  ```
74
74
  solana_contradiction_check({ claims: [{ source: "src1", claim: "bullish", confidence: 0.8 }, { source: "src2", claim: "bearish", confidence: 0.7 }] })
75
75
  ```
76
- Log the contradiction. Default to the more cautious signal (risk/exit > ca_drop).
76
+ Log contradiction. Default to more cautious signal (risk/exit > ca_drop).
77
77
 
78
- **Historical context:** For tokens that appear in alpha signals, check prior signal history:
79
- ```
80
- solana_alpha_history({ tokenAddress: "CA", limit: 10 })
81
- ```
82
- If this token was called before and the outcome was a loss, apply the re-entry penalty (-0.15 confidence).
78
+ **Historical context:** Check prior signal history:
79
+ `solana_alpha_history({ tokenAddress: "CA", limit: 10 })`
80
+ If called before and outcome was a loss, apply re-entry penalty (-0.15 confidence).
83
81
 
84
82
  ## STEP 2: ANALYZE
85
83
 
86
- For top candidates, call ALL of these — no exceptions:
84
+ For top candidates, call ALL — no exceptions:
87
85
  - `solana_token_snapshot` — price, volume, OHLC, trade count
88
86
  - `solana_token_holders` — holder distribution, concentration, dev holdings
89
87
  - `solana_token_flows` — buy/sell pressure, unique traders
90
88
  - `solana_token_liquidity` — pool depth, DEX breakdown
91
89
  - `solana_token_risk` — composite risk profile
92
- - `solana_token_socials` — social media / community metadata (Twitter/X, Telegram, Discord, website)
90
+ - `solana_token_socials` — social media / community metadata
93
91
 
94
92
  **FRESH token deep scan (mandatory for tokens < 1h old):**
95
93
  ```
@@ -97,57 +95,36 @@ solana_bitquery_catalog({ templatePath: "pumpFunHoldersRisk.first100Buyers", var
97
95
  solana_compute_deployer_risk({ previousTokens: N, rugHistory: R, avgTokenLifespanHours: H })
98
96
  solana_deployer_trust_get({ address: "<deployer_address>" })
99
97
  ```
100
- The first100Buyers template reveals serial dumpers and insider clusters. The deployer risk tools give you a deterministic HIGH/MEDIUM/LOW classification. If deployer is HIGH risk → hard skip. If MEDIUM → reduce sizing by 50%.
98
+ first100Buyers reveals serial dumpers and insider clusters. Deployer risk gives deterministic HIGH/MEDIUM/LOW. HIGH risk → hard skip. MEDIUM → reduce sizing by 50%.
101
99
 
102
100
  **Candidate recording (mandatory for EVERY analyzed token):**
103
101
  ```
104
- solana_candidate_write({
105
- id: "CA",
106
- tokenAddress: "CA",
107
- tokenSymbol: "SYMBOL",
108
- source: "scan|alpha|manual",
109
- signalScore: 75,
110
- signalStage: "early|confirmation|milestone|risk|exit",
111
- features: { volume_momentum: 0.8, buy_pressure: 0.6, liquidity: 0.7, holder_quality: 0.5 }
112
- })
102
+ solana_candidate_write({ id: "CA", tokenAddress: "CA", tokenSymbol: "SYMBOL", source: "scan|alpha|manual", signalScore: 75, signalStage: "early|confirmation|milestone|risk|exit", features: { volume_momentum: 0.8, buy_pressure: 0.6, liquidity: 0.7, holder_quality: 0.5 } })
113
103
  ```
114
- Record the candidate with features BEFORE deciding whether to trade. This feeds the intelligence lab dataset. Every analyzed token gets written, whether you trade it or skip it.
104
+ Record with features BEFORE deciding whether to trade. Feeds the intelligence lab dataset. Every analyzed token gets written, whether you trade it or skip it.
115
105
 
116
106
  **Social intel (mandatory for any token scoring above 0.60):**
117
107
 
118
- First, get structured social metadata from the API:
119
- ```
120
- solana_token_socials({ tokenAddress: "CA" })
121
- ```
122
- This returns Twitter/X handle, Telegram, Discord, website links. Use these for cross-referencing with on-chain metadata and X search results.
108
+ Get structured social metadata: `solana_token_socials({ tokenAddress: "CA" })` — returns Twitter/X, Telegram, Discord, website links for cross-referencing.
123
109
 
124
- Then search X/Twitter for real-time sentiment:
125
- ```
126
- x_search_tweets({ query: "$SYMBOL" })
127
- ```
128
- Check mention velocity, influencer clustering, sentiment tone. Cross-check any X handles found via `solana_token_socials` with actual tweet activity. If X tools fail, log the error and continue — but you MUST attempt the call. Skipping social intel is a violation.
110
+ Search X/Twitter for real-time sentiment: `x_search_tweets({ query: "$SYMBOL" })`
111
+ Check mention velocity, influencer clustering, sentiment tone. Cross-check X handles from `solana_token_socials` with actual tweet activity. If X tools fail, log error and continue — but you MUST attempt the call.
129
112
 
130
113
  **Prompt scrubbing (mandatory for all external text):**
131
- Before using any tweet content, Discord message, or website text in trading decisions, scrub it:
132
- ```
133
- solana_scrub_untrusted_text({ text: "<raw external text>", maxLength: 500 })
134
- ```
114
+ `solana_scrub_untrusted_text({ text: "<raw external text>", maxLength: 500 })`
135
115
 
136
116
  **Website legitimacy check (mandatory for any token scoring above 0.60):**
137
- 1. Check if `solana_token_socials` returned a website URL. If not, get on-chain metadata: `solana_bitquery_catalog({ templatePath: "pumpFunMetadata.tokenMetadataByAddress", variables: { token: "CA" } })`
138
- 2. If metadata contains a `website` field, fetch it:
139
- ```
140
- web_fetch_url({ url: "<website_url>" })
141
- ```
142
- 3. Analyze the result the tool returns `title`, `metaDescription`, `headings`, `socialLinks`, `outboundLinks`, `bodyText`.
143
- 4. Apply confidence adjustments:
144
- - Professional site with consistent social links (website twitter matches on-chain metadata twitter) +0.02
145
- - No website at all neutral (many legit memecoins have no site)
146
- - Website exists but generic template with no real content → -0.01
147
- - Website social links don't match on-chain metadata → -0.03 (red flag)
148
- 5. Cache rule: check `solana_memory_search` for `website_analyzed` before fetching. If same URL was analyzed in last 48h, reuse the cached result. After analysis, write findings via `solana_memory_write` with tag `website_analyzed`.
149
-
150
- **Token lifecycle classification (drives everything downstream):**
117
+ 1. Check if `solana_token_socials` returned a website URL. If not: `solana_bitquery_catalog({ templatePath: "pumpFunMetadata.tokenMetadataByAddress", variables: { token: "CA" } })`
118
+ 2. If website found, fetch it: `web_fetch_url({ url: "<website_url>" })`
119
+ 3. Analyze — tool returns `title`, `metaDescription`, `headings`, `socialLinks`, `outboundLinks`, `bodyText`.
120
+ 4. Confidence adjustments:
121
+ - Professional site with matching social links → +0.02
122
+ - No website neutral (many legit memecoins have no site)
123
+ - Generic template with no real content → -0.01
124
+ - Social links don't match on-chain metadata → -0.03 (red flag)
125
+ 5. Cache: check `solana_memory_search` for `website_analyzed` before fetching. If analyzed in last 48h, reuse. After analysis, write via `solana_memory_write` tag `website_analyzed`.
126
+
127
+ **Token lifecycle classification:**
151
128
  - FRESH (< 1h): Mint MUST be revoked, freeze MUST be inactive, LP MUST be burned/locked. Serial deployer (3+ tokens/24h) = hard skip. Volume >70% in first 15min = skip. EXPLORATORY SIZING ONLY (3-5% capital HARDENED, exploratory range DEGEN).
152
129
  - EMERGING (1-24h): Top-10 concentration declining? Volume >20% of peak hour? Standard sizing.
153
130
  - ESTABLISHED (>24h): Full sizing. Edge = flow analysis + narrative timing.
@@ -155,18 +132,14 @@ web_fetch_url({ url: "<website_url>" })
155
132
  ## STEP 3: RISK & SCORING
156
133
 
157
134
  **Freshness decay (mandatory):**
158
- ```
159
- solana_compute_freshness_decay({ signalAgeMinutes: N, halfLifeMinutes: 30 })
160
- ```
161
- Apply the returned decay factor to alpha signal scores. Older signals carry less weight.
135
+ `solana_compute_freshness_decay({ signalAgeMinutes: N, halfLifeMinutes: 30 })`
136
+ Apply returned decay factor to alpha signal scores.
162
137
 
163
138
  **Use `solana_compute_confidence` — NEVER do manual math.** The tool returns deterministic results.
164
139
 
165
140
  **Champion model scoring (if model exists):**
166
- ```
167
- solana_model_score_candidate({ modelId: "champion", features: { volume_momentum: 0.8, buy_pressure: 0.6, ... } })
168
- ```
169
- If the champion model returns a score that diverges from `compute_confidence` by more than 0.15, log the divergence via `solana_memory_write` with tag `model_divergence`. Use the more conservative score for the trade decision.
141
+ `solana_model_score_candidate({ modelId: "champion", features: { volume_momentum: 0.8, buy_pressure: 0.6, ... } })`
142
+ If score diverges from `compute_confidence` by >0.15, log via `solana_memory_write` tag `model_divergence`. Use the more conservative score.
170
143
 
171
144
  **FOMO check BEFORE computing confidence:**
172
145
  - Already moved +500% in <4h → skip
@@ -189,7 +162,7 @@ If the champion model returns a score that diverges from `compute_confidence` by
189
162
 
190
163
  **Hard caps (non-negotiable):**
191
164
  - Position ≤ 2% of pool depth in USD. Pool < $50K → max $1,000 SOL equivalent.
192
- - Mint authority active OR freeze authority active → HARD SKIP. No exceptions.
165
+ - Mint authority active OR freeze authority active → HARD SKIP.
193
166
  - Max 40% capital across same narrative cluster.
194
167
 
195
168
  **Sizing reduction triggers (stack multiplicatively):**
@@ -212,18 +185,18 @@ If the champion model returns a score that diverges from `compute_confidence` by
212
185
  **CRITICAL:** Every `solana_trade_execute` call MUST include `tpExits` with multiple levels:
213
186
  ```
214
187
  tpExits: [
215
- { percent: 100, amountPct: 30 }, // Sell 30% at +100%
216
- { percent: 200, amountPct: 100 } // Sell remaining at +200%
188
+ { percent: 100, amountPct: 30 },
189
+ { percent: 200, amountPct: 100 }
217
190
  ]
218
191
  ```
219
192
  HARDENED range: +100–300%. DEGEN range: +200–500%. `percent` = price increase from entry, `amountPct` = % of position to sell.
220
193
 
221
- **Use structured `trailingStop` with `levels` array** (preferred over legacy `trailingStopPct`):
222
- - `percentage` — trailing drawdown % from the armed high once that level is active.
223
- - `amount` — % of position to sell at this level (1–100; server default `100`).
224
- - `triggerAboveATH` — **optional.** Price must reach this % above the session ATH before this level arms. Default `100` (2× ATH). Use smaller value (e.g. `25`) to arm earlier. Use `trailingStopPct` for simpler single-level trailing without this gate.
194
+ **Structured `trailingStop` with `levels` array** (preferred over legacy `trailingStopPct`):
195
+ - `percentage` — trailing drawdown % from armed high once level is active.
196
+ - `amount` — % of position to sell (1–100; server default `100`).
197
+ - `triggerAboveATH` — **optional.** Price must reach this % above session ATH before level arms. Default `100` (2× ATH). Use `trailingStopPct` for simpler single-level trailing.
225
198
 
226
- **`slExits` for graduated stop-losses** — e.g., `[{ percent: 20, amountPct: 100 }]` (HARDENED) or `[{ percent: 40, amountPct: 100 }]` (DEGEN). Use `percent` = price decrease from entry, `amountPct` = % of remaining position to sell.
199
+ **`slExits`** — e.g., `[{ percent: 20, amountPct: 100 }]` (HARDENED) or `[{ percent: 40, amountPct: 100 }]` (DEGEN). `percent` = price decrease from entry, `amountPct` = % of remaining position to sell.
227
200
 
228
201
  **Slippage:** >$500K pool = 100-200bps, $100-500K = 200-400bps, $50-100K = 300-500bps, <$50K = 400-800bps (cap). Exit = 1.5× entry.
229
202
 
@@ -233,36 +206,15 @@ HARDENED range: +100–300%. DEGEN range: +200–500%. `percent` = price increas
233
206
 
234
207
  **Pre-trade journal FIRST** — call `solana_memory_write` with tag `pre_trade_rationale` BEFORE executing. Also call `solana_decision_log` to record the decision with confidence, sizing rationale, and risk factors.
235
208
 
236
- **Source attribution (mandatory):** The `pre_trade_rationale` memory entry MUST include `source: "<how you found this token>"` — one of: `alpha_signal:<source_name>`, `scan_launches`, `scan_hot_pairs`, `bitquery_subscription`, `manual`, `watchlist`. This is required for source trust scoring and strategy evolution to correlate wins/losses with discovery channels.
209
+ **Source attribution (mandatory):** The `pre_trade_rationale` MUST include `source: "<how you found this token>"` — one of: `alpha_signal:<source_name>`, `scan_launches`, `scan_hot_pairs`, `bitquery_subscription`, `manual`, `watchlist`. Required for source trust scoring and strategy evolution.
237
210
 
238
211
  **Prior history check (mandatory):**
239
- ```
240
- solana_memory_by_token({ tokenAddress: "CA" })
241
- ```
242
- Check for prior trades on this token. If you lost money on it before, the re-entry penalty (-0.15) must already be factored into your confidence score.
212
+ `solana_memory_by_token({ tokenAddress: "CA" })`
213
+ If you lost money on this token before, re-entry penalty (-0.15) must already be factored into confidence.
243
214
 
244
215
  **REQUIRED PARAMETERS FOR solana_trade_execute:**
245
-
246
- ```javascript
247
- solana_trade_execute({
248
- tokenAddress: "CA",
249
- side: "buy",
250
- symbol: "SYMBOL",
251
- sizeSol: X,
252
- slPct: 20,
253
- tpExits: [
254
- { percent: 100, amountPct: 30 },
255
- { percent: 200, amountPct: 100 }
256
- ],
257
- trailingStop: {
258
- levels: [
259
- { percentage: 25, amount: 50 },
260
- { percentage: 35, amount: 100, triggerAboveATH: 100 }
261
- ]
262
- },
263
- slippageBps: 300, // REQUIRED — always send
264
- idempotencyKey: "unique-id"
265
- })
216
+ ```
217
+ solana_trade_execute({ tokenAddress: "CA", side: "buy", symbol: "SYMBOL", sizeSol: X, slPct: 20, tpExits: [{ percent: 100, amountPct: 30 }, { percent: 200, amountPct: 100 }], trailingStop: { levels: [{ percentage: 25, amount: 50 }, { percentage: 35, amount: 100, triggerAboveATH: 100 }] }, slippageBps: 300, idempotencyKey: "unique-id" })
266
218
  ```
267
219
 
268
220
  **ABSOLUTE RULES:**
@@ -271,13 +223,9 @@ solana_trade_execute({
271
223
  - ❌ NEVER use tpLevels alone (defaults to 100% exit per level)
272
224
  - ✅ Always send BOTH tpExits AND slExits
273
225
 
274
- Then call `solana_trade_execute`.
275
-
276
226
  **Post-buy Bitquery subscription (mandatory after successful buy):**
277
- ```
278
- solana_bitquery_subscribe({ templateKey: "realtimeTokenPricesSolana", variables: { token: "CA" }, agentId: "main" })
279
- ```
280
- Start real-time price monitoring for the position. This gives you live price data between heartbeats.
227
+ `solana_bitquery_subscribe({ templateKey: "realtimeTokenPricesSolana", variables: { token: "CA" }, agentId: "main" })`
228
+ Start real-time price monitoring. Live price data between heartbeats.
281
229
 
282
230
  **IMMEDIATELY after execution, post this EXACT format:**
283
231
 
@@ -298,19 +246,14 @@ For each open position: check PnL, SL/TP proximity, flow direction. **Use `unrea
298
246
 
299
247
  **On-chain verification:** If any position balance looks inconsistent, call `solana_wallet_token_balance` with the token mint to verify actual on-chain holdings.
300
248
 
301
- **Feature delta check on held positions (optional but recommended):**
302
- ```
303
- solana_candidate_delta({ id: "CA", currentFeatures: { volume_momentum: 0.5, buy_pressure: 0.3, ... } })
304
- ```
305
- Compare the token's current features against what they were at entry. If features have degraded significantly (e.g., buy pressure flipped negative, volume collapsed), consider exiting even if SL hasn't triggered.
249
+ **Feature delta check (optional but recommended):**
250
+ `solana_candidate_delta({ id: "CA", currentFeatures: { volume_momentum: 0.5, buy_pressure: 0.3, ... } })`
251
+ Compare current features against entry. If degraded significantly (buy pressure flipped, volume collapsed), consider exiting even if SL hasn't triggered.
306
252
 
307
- **Social exhaustion check on held positions:**
308
- ```
309
- x_search_tweets({ query: "$SYMBOL" })
310
- ```
253
+ **Social exhaustion check:** `x_search_tweets({ query: "$SYMBOL" })`
311
254
  Mention velocity declining + price flat/dropping = social exhaustion → consider exit.
312
255
 
313
- **Dead money re-check:** Apply the 4 criteria from Step 0 again. Do NOT wait for the next cycle to exit dead money.
256
+ **Dead money re-check:** Apply the 4 criteria from Step 0 again. Do NOT wait for the next cycle.
314
257
 
315
258
  ## STEP 7: EXIT + ANNOUNCE
316
259
 
@@ -332,33 +275,23 @@ Partial exits → "🔴 PARTIAL EXIT (50%): SYMBOL (CA)"
332
275
 
333
276
  1. Call `solana_trade_review` for each closed position.
334
277
 
335
- 2. **LABEL THE OUTCOME — CRITICAL, DO NOT SKIP:**
336
- ```
337
- solana_candidate_label_outcome({ id: "CA", outcome: "win|loss|skip|dead_money", pnlPct: X.XX, holdingHours: H })
338
- ```
339
- The intelligence lab CANNOT learn without labeled outcomes. An unlabeled exit is wasted data. If you skip this call, the entire learning pipeline is broken — strategy evolution, model evaluation, and replay all depend on labeled candidates.
278
+ 2. **LABEL THE OUTCOME — DO NOT SKIP:**
279
+ `solana_candidate_label_outcome({ id: "CA", outcome: "win|loss|skip|dead_money", pnlPct: X.XX, holdingHours: H })`
280
+ The intelligence lab CANNOT learn without labeled outcomes. Strategy evolution, model evaluation, and replay all depend on labeled candidates.
340
281
 
341
282
  3. **LEARNING ENTRY — REQUIRED after every loss or dead_money exit:**
342
283
  ```
343
- solana_memory_write({
344
- content: "LEARNING ENTRY: LRN-YYYYMMDD-NNN\nPriority: P2\nArea: <area_tag>\nWHAT HAPPENED: <1 sentence>\nWHY IT WENT WRONG: <root cause>\nEVIDENCE: token CA, entry price, exit price, hold time\nSUGGESTED ADJUSTMENT: <what to change>",
345
- tags: ["learning_entry", "learning_entry_<area>"]
346
- })
284
+ solana_memory_write({ content: "LEARNING ENTRY: LRN-YYYYMMDD-NNN\nPriority: P2\nArea: <area_tag>\nWHAT HAPPENED: <1 sentence>\nWHY IT WENT WRONG: <root cause>\nEVIDENCE: token CA, entry price, exit price, hold time\nSUGGESTED ADJUSTMENT: <what to change>", tags: ["learning_entry", "learning_entry_<area>"] })
347
285
  ```
348
- Losses without learning entries are the #1 reason the strategy fails to evolve. Check `refs/review-learning.md` for the full entry format and area tags.
286
+ Losses without learning entries are the #1 reason the strategy fails to evolve. See `refs/review-learning.md` for full format and area tags.
349
287
 
350
- 4. Unsubscribe from Bitquery stream for the exited token:
351
- ```
352
- solana_bitquery_unsubscribe({ subscriptionId: "<id>" })
353
- ```
288
+ 4. Unsubscribe from Bitquery stream: `solana_bitquery_unsubscribe({ subscriptionId: "<id>" })`
354
289
 
355
- 5. If this was an alpha-sourced trade, check and record source accuracy:
356
- ```
357
- solana_alpha_history({ tokenAddress: "CA", limit: 5 })
358
- ```
359
- Log the source's accuracy via `solana_memory_write` with tag `source_reputation`.
290
+ 5. If alpha-sourced trade, check source accuracy:
291
+ `solana_alpha_history({ tokenAddress: "CA", limit: 5 })`
292
+ Log via `solana_memory_write` with tag `source_reputation`.
360
293
 
361
- ## STEP 8: MEMORY WRITE-BACK (mandatory — call ALL of these)
294
+ ## STEP 8: MEMORY WRITE-BACK (mandatory — call ALL)
362
295
 
363
296
  - `solana_state_save` if any durable state changed
364
297
  - `solana_daily_log` with cycle summary
@@ -369,11 +302,11 @@ Log the source's accuracy via `solana_memory_write` with tag `source_reputation`
369
302
  - `solana_context_snapshot_write` — write portfolio world-view for bootstrap injection
370
303
 
371
304
  **Self-check before completing Step 8:**
372
- - Did you exit any positions this cycle? If yes: did you call `solana_candidate_label_outcome` for EACH exit? If not, go back and call it now.
373
- - Did any exit result in a loss or dead_money? If yes: did you write a `learning_entry` via `solana_memory_write`? If not, write one now.
374
- - Did you execute any trades this cycle? If yes: does each `pre_trade_rationale` include `source:` attribution? If not, write a correction entry now.
305
+ - Did you exit any positions? did you call `solana_candidate_label_outcome` for EACH? If not, do it now.
306
+ - Any loss or dead_money exit? did you write a `learning_entry`? If not, write one now.
307
+ - Any trades executed? does each `pre_trade_rationale` include `source:` attribution? If not, write correction now.
375
308
 
376
- Do NOT skip the last three. They are not optional memory — they feed the bootstrap digest that loads into your next session.
309
+ Do NOT skip these. They feed the bootstrap digest that loads into your next session.
377
310
 
378
311
  ## STEP 9: REPORT TO USER
379
312
 
@@ -400,18 +333,18 @@ OPEN POSITIONS:
400
333
  [or "No open positions"]
401
334
 
402
335
  SKIPPED:
403
- - SYMBOL (full_CA): reason skipped (e.g., "FOMO +320% already", "mint authority active", "confidence 0.48 < threshold")
336
+ - SYMBOL (full_CA): reason skipped
404
337
  [or "No candidates reached analysis"]
405
338
 
406
339
  NEXT CYCLE: [1 sentence — what you're watching for]
407
340
  ```
408
341
 
409
342
  **MANDATORY FORMAT RULES:**
410
- - Every token MUST be SYMBOL (full_contract_address). NO EXCEPTIONS. "BERENSTAIN" alone is INVALID. It must be "BERENSTAIN (full_CA_here)".
411
- - PnL numbers must come from `solana_positions` or `solana_trade_review` tool output on `solana_positions`, always read `unrealizedPnl` / `realizedPnl` for SOL values. NEVER calculate PnL manually. NEVER estimate. If the tool didn't return it, say "PnL: pending".
412
- - Capital must come from `solana_capital_status`. NEVER estimate capital.
413
- - The DEEP ANALYSIS section is MANDATORY. Omitting it is a violation. If you used zero advanced tools, say so explicitly (e.g., "none — no FRESH tokens"). Lying about tool usage is worse than not using the tools.
414
- - Keep under 60 lines. This is a cycle summary, not a session essay.
343
+ - Every token MUST be SYMBOL (full_contract_address). NO EXCEPTIONS.
344
+ - PnL must come from `solana_positions` or `solana_trade_review` — use `unrealizedPnl` / `realizedPnl` for SOL values. NEVER calculate manually. If tool didn't return it, say "PnL: pending".
345
+ - Capital must come from `solana_capital_status`. NEVER estimate.
346
+ - DEEP ANALYSIS section is MANDATORY. If zero advanced tools used, say so explicitly.
347
+ - Keep under 60 lines.
415
348
 
416
349
  ---
417
350
 
@@ -1,6 +1,98 @@
1
- # solana-trader — Durable Memory
1
+ # MEMORY.md
2
2
 
3
3
  > Auto-generated by solana_state_save. OpenClaw loads this file into context at every session start.
4
4
  > Last updated: pending first startup
5
5
 
6
6
  _No state saved yet._
7
+
8
+ <!--
9
+ Expected structure after first solana_state_save call:
10
+
11
+ ## Trading Identity
12
+ - Tier: (free|pro|whale)
13
+ - Wallet: (public key)
14
+ - Mode: (HARDENED|DEGEN)
15
+ - Strategy Version: (semver)
16
+ - Min Buy: (minimum SOL per trade)
17
+ - Min TP: (minimum take-profit %)
18
+ - TP Structure:
19
+ - (TP configuration details)
20
+
21
+ ---
22
+
23
+ ## STRATEGY
24
+
25
+ ### Feature Weights
26
+ - volume_momentum: 0.20
27
+ - buy_pressure: 0.20
28
+ - flow_divergence: 0.15
29
+ - liquidity_depth: 0.15
30
+ - risk_inverse: 0.15
31
+ - token_maturity: 0.10
32
+ - holder_quality: 0.05
33
+
34
+ ### Position Sizing Rules
35
+ - 0.85+ (SUPER ALPHA): (sizing)
36
+ - 0.75-0.84 (Tier-1): (sizing)
37
+ - 0.65-0.74 (Tier-2): (sizing)
38
+ - 0.50-0.64 (Weak): (sizing)
39
+ - <0.50: (skip)
40
+
41
+ ### Stop-Loss Rules
42
+ - FRESH (0-5 min): (SL%)
43
+ - EMERGING (5-30 min): (SL%)
44
+ - ESTABLISHED (30+ min): (SL%)
45
+
46
+ ### Take-Profit Rules
47
+ - HARD RULE: (rule)
48
+ - 0.50-0.64: (TP%)
49
+ - 0.65-0.79: (TP%)
50
+ - 0.80+: (TP%)
51
+
52
+ ### Entry Filter
53
+ - SUPER ALPHA (0.70+): (action)
54
+ - Tier-1 (0.65-0.69): (action)
55
+ - Tier-2 (0.60-0.64): (action)
56
+ - Below 0.60: (skip)
57
+
58
+ ### Dead Money Rule
59
+ - Force close if: (criteria)
60
+ - No hope trades: (action)
61
+
62
+ ---
63
+
64
+ ## Permanent Learnings
65
+ 1. (lesson 1)
66
+ 2. (lesson 2)
67
+
68
+ ---
69
+
70
+ ## Capital Status Tracker
71
+ Last Updated: (timestamp)
72
+ - Wallet Balance: (SOL)
73
+ - Realized P&L (All-Time): (SOL)
74
+ - Open Positions: (count)
75
+ - Daily Notional Used: (SOL)
76
+ - Daily Realized Loss: (SOL)
77
+ - Kill Switch: (on/off)
78
+
79
+ ---
80
+
81
+ ## Next Trading Cycle
82
+ - Scan: (what to scan)
83
+ - Candidates in Lab: (count)
84
+ - Strategy: (approach)
85
+ - Focus: (narrative/sector)
86
+ - Action: (next step)
87
+
88
+ State keys the agent should use with solana_state_save:
89
+ - tier, walletId, mode, strategyVersion, minBuy, minTp, tpStructure
90
+ - strategy: { featureWeights, positionSizing, stopLoss, takeProfit, entryFilter, deadMoney }
91
+ (also accepted as flat keys: featureWeights, positionSizing, etc.)
92
+ - permanentLearnings: string[]
93
+ - capitalStatus: { lastUpdated, walletBalance, realizedPnlAllTime, openPositions, dailyNotionalUsed, dailyRealizedLoss, killSwitch }
94
+ - nextCycle: { scan, candidatesInLab, strategy, focus, action }
95
+ - watchlist: string[]
96
+ - regimeCanary: { regime, detectedAt }
97
+ - preferences: { key: value }
98
+ -->