repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | import type { Book, Candle } from "./state.js"; |
| #2 | import type { TickEntry } from "./journal.js"; |
| #3 | import type { Decision, RalphConfig } from "./validate.js"; |
| #4 | import type { WhaleActivity } from "./observe.js"; |
| #5 | |
| #6 | export interface Observations { |
| #7 | tick: number; |
| #8 | now: string; |
| #9 | mode: "paper"; |
| #10 | network: "devnet"; |
| #11 | candles: Candle[]; |
| #12 | whale_activity: WhaleActivity; |
| #13 | book: Book; |
| #14 | last_decisions: TickEntry[]; |
| #15 | config: Pick<RalphConfig, "goblin" | "max_position_size_lamports" | "loss_killswitch_consecutive" | "model">; |
| #16 | } |
| #17 | |
| #18 | export function deterministicDecision(obs: Observations): Decision { |
| #19 | const candles = obs.candles; |
| #20 | const latest = candles.at(-1); |
| #21 | const previous = candles.at(-2); |
| #22 | if (!latest || !previous || candles.length < 3) { |
| #23 | return { action: "hold", reason: "insufficient candles for a legal devnet paper decision" }; |
| #24 | } |
| #25 | |
| #26 | const openPosition = obs.book.positions[0]; |
| #27 | const momentum = latest.c - previous.c; |
| #28 | const momentumPct = momentum / previous.c; |
| #29 | const whaleBias = obs.whale_activity.bias; |
| #30 | const confidence = Math.min(1, Math.abs(momentumPct) * 80 + (whaleBias !== "neutral" ? 0.25 : 0)); |
| #31 | const desiredSide = momentum >= 0 ? "long" : "short"; |
| #32 | const whaleAgainst = whaleBias !== "neutral" && whaleBias !== desiredSide; |
| #33 | const oneAwayFromKill = obs.last_decisions.at(-1)?.consecutive_losses === obs.config.loss_killswitch_consecutive - 1; |
| #34 | |
| #35 | if (openPosition) { |
| #36 | const shouldClose = |
| #37 | (openPosition.side === "long" && momentum < 0 && !whaleAgainst) || |
| #38 | (openPosition.side === "short" && momentum > 0 && !whaleAgainst) || |
| #39 | oneAwayFromKill; |
| #40 | if (shouldClose) { |
| #41 | return { |
| #42 | action: "close", |
| #43 | position_id: openPosition.id, |
| #44 | reason: `paper signal reversed or risk tightened; closing ${openPosition.side} before the shell cracks`, |
| #45 | }; |
| #46 | } |
| #47 | return { action: "hold", reason: "position already open and signal is not strong enough to churn" }; |
| #48 | } |
| #49 | |
| #50 | const threshold = obs.config.goblin ? 0.5 : 0.7; |
| #51 | if (confidence >= threshold && !whaleAgainst) { |
| #52 | return { |
| #53 | action: "open", |
| #54 | side: desiredSide, |
| #55 | size_lamports: obs.config.max_position_size_lamports, |
| #56 | reason: `${desiredSide} momentum plus ${whaleBias} whale flow clears goblin paper threshold`, |
| #57 | }; |
| #58 | } |
| #59 | |
| #60 | return { action: "hold", reason: "signal exists but whale flow or confidence blocks a safe paper entry" }; |
| #61 | } |
| #62 | |
| #63 | export async function claudeDecision(obs: Observations, systemPrompt: string): Promise<unknown> { |
| #64 | const apiKey = process.env["ANTHROPIC_API_KEY"]; |
| #65 | if (!apiKey) return deterministicDecision(obs); |
| #66 | |
| #67 | const response = await fetch("https://api.anthropic.com/v1/messages", { |
| #68 | method: "POST", |
| #69 | headers: { |
| #70 | "anthropic-version": "2023-06-01", |
| #71 | "content-type": "application/json", |
| #72 | "x-api-key": apiKey, |
| #73 | }, |
| #74 | body: JSON.stringify({ |
| #75 | model: process.env["GOBLIN_MODEL"] || obs.config.model || "claude-opus-4-7", |
| #76 | max_tokens: 220, |
| #77 | temperature: obs.config.goblin ? 0.55 : 0.2, |
| #78 | system: systemPrompt, |
| #79 | messages: [ |
| #80 | { |
| #81 | role: "user", |
| #82 | content: `OBSERVATIONS AT TICK ${obs.tick}\n${JSON.stringify(obs, null, 2)}\n\nReturn only one JSON object.`, |
| #83 | }, |
| #84 | ], |
| #85 | }), |
| #86 | }); |
| #87 | |
| #88 | if (!response.ok) { |
| #89 | return { |
| #90 | action: "hold", |
| #91 | reason: `decision API error ${response.status}; holding paper position safely`, |
| #92 | }; |
| #93 | } |
| #94 | |
| #95 | const payload = (await response.json()) as { content?: Array<{ type: string; text?: string }> }; |
| #96 | const text = payload.content?.find((part) => part.type === "text")?.text?.trim() ?? ""; |
| #97 | const match = text.match(/\{[\s\S]*\}/); |
| #98 | if (!match) return { action: "hold", reason: "model returned no JSON object; paper hold enforced" }; |
| #99 | try { |
| #100 | return JSON.parse(match[0]); |
| #101 | } catch { |
| #102 | return { action: "hold", reason: "model returned invalid JSON; paper hold enforced" }; |
| #103 | } |
| #104 | } |
| #105 | |
| #106 |