hunch-cup 0.2.2 → 0.2.3

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/cli.js CHANGED
@@ -20,7 +20,7 @@
20
20
  import { CupClient } from "./client.js";
21
21
  import { fromPrivateKey, newWallet } from "./signer.js";
22
22
  import { fleetNew, fleetRun, fleetBoard } from "./fleet.js";
23
- const STRATEGIES = ["momentum", "contrarian", "news-reactive"];
23
+ const STRATEGIES = ["momentum", "contrarian", "news-reactive", "random"];
24
24
  function baseUrl() {
25
25
  return process.env.HUNCH_CUP_BASE_URL;
26
26
  }
package/dist/client.d.ts CHANGED
@@ -115,11 +115,15 @@ export declare class CupClient {
115
115
  runStrategy(opts: {
116
116
  strategy: CupStrategy;
117
117
  sizePhunch: number;
118
+ /** When set, each bet's size is a random integer in [sizePhunch, sizePhunchMax]. */
119
+ sizePhunchMax?: number;
118
120
  limit?: number;
119
121
  sentiment?: number;
120
122
  live?: boolean;
121
123
  /** Max fraction of the current balance to deploy per round (default 25%). */
122
124
  maxRoundFraction?: number;
125
+ /** Trade only markets this predicate keeps (e.g. only the flip + 5m/15m rounds). */
126
+ marketFilter?: (m: CupMarket) => boolean;
123
127
  }): Promise<CupTradeReceipt[]>;
124
128
  /**
125
129
  * Run a strategy on a fixed interval, forever (or `rounds` times), catching
@@ -129,11 +133,13 @@ export declare class CupClient {
129
133
  runLoop(opts: {
130
134
  strategy: CupStrategy;
131
135
  sizePhunch: number;
136
+ sizePhunchMax?: number;
132
137
  intervalMs: number;
133
138
  limit?: number;
134
139
  sentiment?: number;
135
140
  live?: boolean;
136
141
  maxRoundFraction?: number;
142
+ marketFilter?: (m: CupMarket) => boolean;
137
143
  rounds?: number;
138
144
  onRound?: (round: number, receipts: CupTradeReceipt[]) => void;
139
145
  onError?: (round: number, err: unknown) => void;
package/dist/client.js CHANGED
@@ -136,7 +136,8 @@ export class CupClient {
136
136
  * The decision is pure ({@link pickFor}); placement is deterministic code.
137
137
  */
138
138
  async runStrategy(opts) {
139
- const markets = await this.listMarkets(opts.limit ?? 10);
139
+ const all = await this.listMarkets(opts.limit ?? 10);
140
+ const markets = opts.marketFilter ? all.filter(opts.marketFilter) : all;
140
141
  const receipts = [];
141
142
  // Bankroll snapshot (live only — a simulate run writes nothing, so it can
142
143
  // fan out over every market without spending).
@@ -160,7 +161,9 @@ export class CupClient {
160
161
  for (const m of markets) {
161
162
  if (opts.live && held.has(m.marketId))
162
163
  continue; // already positioned
163
- const size = opts.sizePhunch;
164
+ const size = opts.sizePhunchMax && opts.sizePhunchMax > opts.sizePhunch
165
+ ? randInt(opts.sizePhunch, opts.sizePhunchMax)
166
+ : opts.sizePhunch;
164
167
  if (opts.live && (balance < size || roundBudget < size))
165
168
  break; // insolvent / budget spent
166
169
  const card = {
@@ -219,6 +222,10 @@ function roundBucket() {
219
222
  function sleep(ms) {
220
223
  return new Promise((resolve) => setTimeout(resolve, ms));
221
224
  }
225
+ /** Uniform random integer in [min, max]. */
226
+ function randInt(min, max) {
227
+ return Math.floor(Math.random() * (max - min + 1)) + min;
228
+ }
222
229
  function randomId() {
223
230
  // Best-effort unique id; the server idempotency key is the full tradeId.
224
231
  return Math.abs(Math.floor(Math.random() * 1e15)).toString(36) + Date.now().toString(36);
package/dist/fleet.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type CupMarket } from "./client.js";
1
2
  import type { CupStrategy } from "./strategy.js";
2
3
  export interface FleetWallet {
3
4
  name: string;
@@ -33,6 +34,31 @@ export declare function fleetRun(opts: {
33
34
  rounds?: number;
34
35
  onEvent?: (line: string) => void;
35
36
  }): Promise<void>;
37
+ /**
38
+ * Coordinated round loop: instead of every wallet running its own perpetual
39
+ * loop, each ROUND a random subset of `participants()` wallets acts once (via
40
+ * `runStrategy`). Models a live crowd where a variable slice of the roster
41
+ * trades each round, not all of them every time. Concurrency across the chosen
42
+ * wallets, staggered so they don't fire on the same instant. Never throws —
43
+ * per-wallet errors surface via `onEvent`.
44
+ */
45
+ export declare function fleetRunRounds(opts: {
46
+ strategy: CupStrategy;
47
+ sizePhunch: number;
48
+ /** When set, each bet's size is a random int in [sizePhunch, sizePhunchMax]. */
49
+ sizePhunchMax?: number;
50
+ intervalMs: number;
51
+ /** How many wallets act this round (default: all). e.g. `() => 70 + rand(0..30)`. */
52
+ participants?: () => number;
53
+ marketFilter?: (m: CupMarket) => boolean;
54
+ maxRoundFraction?: number;
55
+ /** false → simulate (no writes), for a dry-run smoke. Default true (real trades). */
56
+ live?: boolean;
57
+ baseUrl?: string;
58
+ limit?: number;
59
+ rounds?: number;
60
+ onEvent?: (line: string) => void;
61
+ }): Promise<void>;
36
62
  /** Each fleet wallet's live rank + balance + realized PnL, best rank first. */
37
63
  export declare function fleetBoard(baseUrl?: string): Promise<Array<{
38
64
  name: string;
package/dist/fleet.js CHANGED
@@ -97,6 +97,57 @@ export async function fleetRun(opts) {
97
97
  });
98
98
  }));
99
99
  }
100
+ /**
101
+ * Coordinated round loop: instead of every wallet running its own perpetual
102
+ * loop, each ROUND a random subset of `participants()` wallets acts once (via
103
+ * `runStrategy`). Models a live crowd where a variable slice of the roster
104
+ * trades each round, not all of them every time. Concurrency across the chosen
105
+ * wallets, staggered so they don't fire on the same instant. Never throws —
106
+ * per-wallet errors surface via `onEvent`.
107
+ */
108
+ export async function fleetRunRounds(opts) {
109
+ const fleet = loadFleet();
110
+ if (fleet.wallets.length === 0) {
111
+ opts.onEvent?.("No fleet wallets. Run `hunch-cup fleet new <n>` first.");
112
+ return;
113
+ }
114
+ for (let round = 1; !opts.rounds || round <= opts.rounds; round += 1) {
115
+ const n = Math.max(1, Math.min(fleet.wallets.length, Math.floor(opts.participants?.() ?? fleet.wallets.length)));
116
+ const chosen = shuffle(fleet.wallets).slice(0, n);
117
+ const stagger = Math.floor(opts.intervalMs / Math.max(1, chosen.length));
118
+ await Promise.all(chosen.map(async (w, i) => {
119
+ await sleep(stagger * i);
120
+ try {
121
+ const client = new CupClient({ baseUrl: opts.baseUrl, signer: fromPrivateKey(w.privateKey) });
122
+ const receipts = await client.runStrategy({
123
+ strategy: w.strategy ?? opts.strategy,
124
+ sizePhunch: opts.sizePhunch,
125
+ sizePhunchMax: opts.sizePhunchMax,
126
+ limit: opts.limit,
127
+ live: opts.live ?? true,
128
+ maxRoundFraction: opts.maxRoundFraction,
129
+ marketFilter: opts.marketFilter,
130
+ });
131
+ opts.onEvent?.(`[${w.name}] round ${round}: ${receipts.length} trade(s)`);
132
+ }
133
+ catch (err) {
134
+ opts.onEvent?.(`[${w.name}] round ${round} error: ${err instanceof Error ? err.message : String(err)}`);
135
+ }
136
+ }));
137
+ if (opts.rounds && round >= opts.rounds)
138
+ break;
139
+ await sleep(opts.intervalMs);
140
+ }
141
+ }
142
+ /** Fisher–Yates shuffle (copy) — pick a random subset without bias. */
143
+ function shuffle(arr) {
144
+ const a = [...arr];
145
+ for (let i = a.length - 1; i > 0; i -= 1) {
146
+ const j = Math.floor(Math.random() * (i + 1));
147
+ [a[i], a[j]] = [a[j], a[i]];
148
+ }
149
+ return a;
150
+ }
100
151
  /** Each fleet wallet's live rank + balance + realized PnL, best rank first. */
101
152
  export async function fleetBoard(baseUrl) {
102
153
  const fleet = loadFleet();
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { CupClientOptions, CupSigner, CupMarket, CupOutcome, CupPosition, C
3
3
  export { fromViemAccount, fromPrivateKey, newWallet } from "./signer.js";
4
4
  export { buildCupTradeMessage } from "./message.js";
5
5
  export type { CupTradeMessageParams } from "./message.js";
6
- export { pickFor, momentumPick, contrarianPick, newsReactivePick, } from "./strategy.js";
6
+ export { pickFor, momentumPick, contrarianPick, newsReactivePick, randomPick, } from "./strategy.js";
7
7
  export type { CupStrategy, StrategyCard, StrategyOutcome, StrategyPick, } from "./strategy.js";
8
- export { fleetNew, fleetRun, fleetBoard, loadFleet, saveFleet, fleetFilePath, } from "./fleet.js";
8
+ export { fleetNew, fleetRun, fleetRunRounds, fleetBoard, loadFleet, saveFleet, fleetFilePath, } from "./fleet.js";
9
9
  export type { Fleet, FleetWallet } from "./fleet.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { CupClient, DEFAULT_BASE_URL } from "./client.js";
2
2
  export { fromViemAccount, fromPrivateKey, newWallet } from "./signer.js";
3
3
  export { buildCupTradeMessage } from "./message.js";
4
- export { pickFor, momentumPick, contrarianPick, newsReactivePick, } from "./strategy.js";
5
- export { fleetNew, fleetRun, fleetBoard, loadFleet, saveFleet, fleetFilePath, } from "./fleet.js";
4
+ export { pickFor, momentumPick, contrarianPick, newsReactivePick, randomPick, } from "./strategy.js";
5
+ export { fleetNew, fleetRun, fleetRunRounds, fleetBoard, loadFleet, saveFleet, fleetFilePath, } from "./fleet.js";
@@ -32,7 +32,7 @@ export interface StrategyPick {
32
32
  slug: string;
33
33
  side: string;
34
34
  }
35
- export type CupStrategy = "momentum" | "contrarian" | "news-reactive";
35
+ export type CupStrategy = "momentum" | "contrarian" | "news-reactive" | "random";
36
36
  /** Momentum: trade WITH the crowd — back the pool's favorite (max odds). */
37
37
  export declare function momentumPick(card: StrategyCard): StrategyPick;
38
38
  /** Contrarian: fade the crowd — back the longest-odds outcome. */
@@ -45,5 +45,12 @@ export declare function contrarianPick(card: StrategyCard): StrategyPick;
45
45
  * crowd favorite (positive) or underdog (negative) — always a real key.
46
46
  */
47
47
  export declare function newsReactivePick(card: StrategyCard, sentiment: number): StrategyPick;
48
+ /**
49
+ * Random: take a side with NO logic — uniformly pick one of the market's real
50
+ * outcome keys (N-way → over `outcomes`; binary/direction → yes/no, the
51
+ * settlement keys). A fleet of these produces liquidity and pool depth with no
52
+ * predictable bias; the `rng` is injectable so the pick is unit-testable.
53
+ */
54
+ export declare function randomPick(card: StrategyCard, rng?: () => number): StrategyPick;
48
55
  /** Dispatch a named strategy. `news-reactive` uses `sentiment` (default 0). */
49
56
  export declare function pickFor(strategy: CupStrategy, card: StrategyCard, sentiment?: number): StrategyPick;
package/dist/strategy.js CHANGED
@@ -69,6 +69,20 @@ export function newsReactivePick(card, sentiment) {
69
69
  return { slug: card.slug, side: sideFor(card, "neg") };
70
70
  return momentumPick(card);
71
71
  }
72
+ /**
73
+ * Random: take a side with NO logic — uniformly pick one of the market's real
74
+ * outcome keys (N-way → over `outcomes`; binary/direction → yes/no, the
75
+ * settlement keys). A fleet of these produces liquidity and pool depth with no
76
+ * predictable bias; the `rng` is injectable so the pick is unit-testable.
77
+ */
78
+ export function randomPick(card, rng = Math.random) {
79
+ if (isNway(card)) {
80
+ const keys = card.outcomes.map((o) => o.key);
81
+ const i = Math.min(Math.floor(rng() * keys.length), keys.length - 1);
82
+ return { slug: card.slug, side: keys[i] ?? keys[0] };
83
+ }
84
+ return { slug: card.slug, side: rng() < 0.5 ? "yes" : "no" };
85
+ }
72
86
  /** Dispatch a named strategy. `news-reactive` uses `sentiment` (default 0). */
73
87
  export function pickFor(strategy, card, sentiment = 0) {
74
88
  switch (strategy) {
@@ -78,5 +92,7 @@ export function pickFor(strategy, card, sentiment = 0) {
78
92
  return contrarianPick(card);
79
93
  case "news-reactive":
80
94
  return newsReactivePick(card, sentiment);
95
+ case "random":
96
+ return randomPick(card);
81
97
  }
82
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hunch-cup",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "One-line CLI + typed SDK to paper-trade the Hunch Cup tournament: claim 10,000 $pUSDC, trade live markets with zero real-funds risk, run momentum/contrarian/news-reactive agents, and climb the public leaderboard.",
5
5
  "keywords": [
6
6
  "hunch",