@palmyr/cli 1.3.0 → 1.4.0
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 +143 -25
- package/dist/cli.js.map +1 -1
- package/dist/wallet-brief-llm.js +5 -3
- package/dist/wallet-brief-llm.js.map +1 -1
- package/dist/wallet-daemon.d.ts +2 -0
- package/dist/wallet-daemon.js +11 -4
- package/dist/wallet-daemon.js.map +1 -1
- package/dist/wallet-live-test.d.ts +49 -0
- package/dist/wallet-live-test.js +162 -0
- package/dist/wallet-live-test.js.map +1 -0
- package/dist/wallet-readiness.d.ts +51 -0
- package/dist/wallet-readiness.js +183 -0
- package/dist/wallet-readiness.js.map +1 -0
- package/dist/wallet-trading.d.ts +57 -44
- package/dist/wallet-trading.js +75 -68
- package/dist/wallet-trading.js.map +1 -1
- package/package.json +1 -1
package/dist/wallet-trading.d.ts
CHANGED
|
@@ -96,36 +96,41 @@ export interface SolanaSell {
|
|
|
96
96
|
time: string;
|
|
97
97
|
tokensIn: string;
|
|
98
98
|
tokensInRaw: string;
|
|
99
|
-
/** Display string. Holds "0.300000 SOL" for native sells, "5.00 USDC" for USDC sells. */
|
|
100
|
-
solOut: string;
|
|
101
|
-
/** Raw output. Lamports for SOL exits, 6-dec raw for USDC exits. Both fit JS Number for realistic sizes. */
|
|
102
|
-
solOutRaw: number;
|
|
103
99
|
percentRequested: number;
|
|
104
|
-
/** Realized PnL in the position's `inputAsset` (SOL or USDC). Field name kept for back-compat. */
|
|
105
|
-
realizedSol: number;
|
|
106
100
|
reason: string;
|
|
107
101
|
feeLamports?: number;
|
|
108
102
|
tipLamports?: number;
|
|
109
103
|
slippageBpsUsed?: number;
|
|
110
104
|
protectedExec?: boolean;
|
|
111
105
|
forensics?: FillForensics;
|
|
112
|
-
/**
|
|
113
|
-
outputAsset
|
|
114
|
-
/** Realized output (asset-tagged).
|
|
115
|
-
output
|
|
116
|
-
/** Realized PnL for this sell (asset-tagged).
|
|
117
|
-
realized
|
|
106
|
+
/** Asset this sell exited to (mirrors entry.inputAsset). */
|
|
107
|
+
outputAsset: SolanaInputAsset;
|
|
108
|
+
/** Realized output (asset-tagged). Holds USDC values on USDC exits — name no longer chain-typed. */
|
|
109
|
+
output: AssetAmount;
|
|
110
|
+
/** Realized PnL for this sell (asset-tagged). */
|
|
111
|
+
realized: AssetPnl;
|
|
112
|
+
/**
|
|
113
|
+
* @deprecated Read `output.display` / `output.raw` / `realized.amount` instead.
|
|
114
|
+
* Optional only so legacy position files on disk (pre-canonical) still parse;
|
|
115
|
+
* new sells no longer set these.
|
|
116
|
+
*/
|
|
117
|
+
solOut?: string;
|
|
118
|
+
/** @deprecated Use `output.raw`. */
|
|
119
|
+
solOutRaw?: number;
|
|
120
|
+
/** @deprecated Use `realized.amount`. */
|
|
121
|
+
realizedSol?: number;
|
|
118
122
|
}
|
|
119
123
|
export interface SolanaPnl {
|
|
120
|
-
/** Realized PnL in the position's input asset (SOL or USDC). Field name kept for back-compat with legacy SOL positions. */
|
|
121
|
-
realizedSol: number;
|
|
122
|
-
unrealizedSol: number;
|
|
123
124
|
unrealizedPct: number;
|
|
124
125
|
lastPricedAt: string | null;
|
|
125
126
|
/** Realized PnL across all sells (asset-tagged). */
|
|
126
|
-
realized
|
|
127
|
+
realized: AssetPnl;
|
|
127
128
|
/** Mark-to-market unrealized PnL (asset-tagged). */
|
|
128
|
-
unrealized
|
|
129
|
+
unrealized: AssetPnl;
|
|
130
|
+
/** @deprecated Use `realized.amount`. */
|
|
131
|
+
realizedSol?: number;
|
|
132
|
+
/** @deprecated Use `unrealized.amount`. */
|
|
133
|
+
unrealizedSol?: number;
|
|
129
134
|
}
|
|
130
135
|
export interface SolanaPositionFile {
|
|
131
136
|
chain: 'solana';
|
|
@@ -167,35 +172,40 @@ export interface BaseSell {
|
|
|
167
172
|
time: string;
|
|
168
173
|
tokensIn: string;
|
|
169
174
|
tokensInRaw: string;
|
|
170
|
-
/** Display string: "0.005000 ETH" or "5.00 USDC". Field name kept for back-compat. */
|
|
171
|
-
ethOut: string;
|
|
172
|
-
/** Raw output. Wei for ETH exits, 6-dec raw for USDC exits. Both kept as string for u256 safety. */
|
|
173
|
-
ethOutRawWei: string;
|
|
174
175
|
percentRequested: number;
|
|
175
|
-
/** Realized PnL in the position's input asset (ETH or USDC). Field name kept for back-compat. */
|
|
176
|
-
realizedEth: number;
|
|
177
176
|
reason: string;
|
|
178
177
|
feeWei?: string;
|
|
179
178
|
slippageBpsUsed?: number;
|
|
180
179
|
protectedExec?: boolean;
|
|
181
180
|
forensics?: FillForensics;
|
|
182
|
-
/**
|
|
183
|
-
outputAsset
|
|
184
|
-
/** Realized output (asset-tagged).
|
|
185
|
-
output
|
|
186
|
-
/** Realized PnL for this sell (asset-tagged).
|
|
187
|
-
realized
|
|
181
|
+
/** Asset this sell exited to (mirrors entry.inputAsset). */
|
|
182
|
+
outputAsset: BaseInputAsset;
|
|
183
|
+
/** Realized output (asset-tagged). Holds USDC values on USDC exits — name no longer chain-typed. */
|
|
184
|
+
output: AssetAmount;
|
|
185
|
+
/** Realized PnL for this sell (asset-tagged). */
|
|
186
|
+
realized: AssetPnl;
|
|
187
|
+
/**
|
|
188
|
+
* @deprecated Read `output.display` / `output.raw` / `realized.amount` instead.
|
|
189
|
+
* Optional only so legacy position files on disk (pre-canonical) still parse;
|
|
190
|
+
* new sells no longer set these.
|
|
191
|
+
*/
|
|
192
|
+
ethOut?: string;
|
|
193
|
+
/** @deprecated Use `output.raw`. */
|
|
194
|
+
ethOutRawWei?: string;
|
|
195
|
+
/** @deprecated Use `realized.amount`. */
|
|
196
|
+
realizedEth?: number;
|
|
188
197
|
}
|
|
189
198
|
export interface BasePnl {
|
|
190
|
-
/** Realized PnL in the position's input asset (ETH or USDC). Field name kept for back-compat. */
|
|
191
|
-
realizedEth: number;
|
|
192
|
-
unrealizedEth: number;
|
|
193
199
|
unrealizedPct: number;
|
|
194
200
|
lastPricedAt: string | null;
|
|
195
201
|
/** Realized PnL across all sells (asset-tagged). */
|
|
196
|
-
realized
|
|
202
|
+
realized: AssetPnl;
|
|
197
203
|
/** Mark-to-market unrealized PnL (asset-tagged). */
|
|
198
|
-
unrealized
|
|
204
|
+
unrealized: AssetPnl;
|
|
205
|
+
/** @deprecated Use `realized.amount`. */
|
|
206
|
+
realizedEth?: number;
|
|
207
|
+
/** @deprecated Use `unrealized.amount`. */
|
|
208
|
+
unrealizedEth?: number;
|
|
199
209
|
}
|
|
200
210
|
export interface BasePositionFile {
|
|
201
211
|
chain: 'base';
|
|
@@ -265,6 +275,7 @@ export type TradeLogLine = {
|
|
|
265
275
|
currentPct: number;
|
|
266
276
|
thresholdPct?: number;
|
|
267
277
|
peakPct?: number;
|
|
278
|
+
drawdownPct?: number;
|
|
268
279
|
thresholdDurationMs?: number;
|
|
269
280
|
elapsedMs?: number;
|
|
270
281
|
llmVerdict?: 'yes' | 'no' | 'unclear';
|
|
@@ -478,6 +489,8 @@ export interface BuyBaseResult {
|
|
|
478
489
|
protectedExec: boolean;
|
|
479
490
|
rpcUrl: string;
|
|
480
491
|
inputAsset: BaseInputAsset;
|
|
492
|
+
/** One-line human-readable summary; safe to print directly. */
|
|
493
|
+
summary: string;
|
|
481
494
|
}
|
|
482
495
|
export declare function buyBase(opts: BuyBaseOpts): Promise<BuyBaseResult>;
|
|
483
496
|
export interface SellBaseOpts {
|
|
@@ -497,9 +510,6 @@ export interface SellBaseResult {
|
|
|
497
510
|
txHash: string;
|
|
498
511
|
tokensIn: string;
|
|
499
512
|
tokensInRaw: string;
|
|
500
|
-
ethOut: string;
|
|
501
|
-
ethOutRawWei: string;
|
|
502
|
-
realizedEth: number;
|
|
503
513
|
positionStatus: 'open' | 'closed';
|
|
504
514
|
wallet: string;
|
|
505
515
|
mint: string;
|
|
@@ -519,10 +529,12 @@ export interface SellBaseResult {
|
|
|
519
529
|
* on partial sells of a drifted position.
|
|
520
530
|
*/
|
|
521
531
|
reconcileDriftRaw?: string;
|
|
522
|
-
/** Canonical, asset-tagged output.
|
|
532
|
+
/** Canonical, asset-tagged output. */
|
|
523
533
|
output: AssetAmount;
|
|
524
|
-
/** Canonical, asset-tagged realized PnL for this sell.
|
|
534
|
+
/** Canonical, asset-tagged realized PnL for this sell. */
|
|
525
535
|
realized: AssetPnl;
|
|
536
|
+
/** One-line human-readable summary; safe to print directly. */
|
|
537
|
+
summary: string;
|
|
526
538
|
}
|
|
527
539
|
export declare function sellBase(opts: SellBaseOpts): Promise<SellBaseResult>;
|
|
528
540
|
export interface SyncBaseOpts {
|
|
@@ -587,6 +599,8 @@ export interface BuyResult {
|
|
|
587
599
|
protectedExec: boolean;
|
|
588
600
|
forensics?: FillForensics;
|
|
589
601
|
inputAsset: SolanaInputAsset;
|
|
602
|
+
/** One-line human-readable summary; safe to print directly. */
|
|
603
|
+
summary: string;
|
|
590
604
|
}
|
|
591
605
|
export declare function buy(opts: BuyOpts): Promise<BuyResult>;
|
|
592
606
|
export interface SellOpts {
|
|
@@ -607,9 +621,6 @@ export interface SellResult {
|
|
|
607
621
|
txSignature: string;
|
|
608
622
|
tokensIn: string;
|
|
609
623
|
tokensInRaw: string;
|
|
610
|
-
solOut: string;
|
|
611
|
-
solOutRaw: number;
|
|
612
|
-
realizedSol: number;
|
|
613
624
|
positionStatus: 'open' | 'closed';
|
|
614
625
|
wallet: string;
|
|
615
626
|
mint: string;
|
|
@@ -622,10 +633,12 @@ export interface SellResult {
|
|
|
622
633
|
slippageSource: 'user' | 'dexscreener' | 'fallback';
|
|
623
634
|
protectedExec: boolean;
|
|
624
635
|
forensics?: FillForensics;
|
|
625
|
-
/** Canonical, asset-tagged output.
|
|
636
|
+
/** Canonical, asset-tagged output. */
|
|
626
637
|
output: AssetAmount;
|
|
627
|
-
/** Canonical, asset-tagged realized PnL for this sell.
|
|
638
|
+
/** Canonical, asset-tagged realized PnL for this sell. */
|
|
628
639
|
realized: AssetPnl;
|
|
640
|
+
/** One-line human-readable summary; safe to print directly. */
|
|
641
|
+
summary: string;
|
|
629
642
|
}
|
|
630
643
|
export declare function sell(opts: SellOpts): Promise<SellResult>;
|
|
631
644
|
export interface SyncOpts {
|
package/dist/wallet-trading.js
CHANGED
|
@@ -194,28 +194,37 @@ export function normalizePosition(p) {
|
|
|
194
194
|
}
|
|
195
195
|
for (const s of p.sells) {
|
|
196
196
|
if (!s.outputAsset)
|
|
197
|
-
s.outputAsset = p.entry.inputAsset;
|
|
198
|
-
// Back-fill canonical fields
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
s.outputAsset = p.entry.inputAsset ?? 'SOL';
|
|
198
|
+
// Back-fill canonical fields from legacy `solOut*` / `realizedSol` if the
|
|
199
|
+
// file pre-dates the canonical schema, then strip the legacy fields so
|
|
200
|
+
// they don't get re-emitted on the next write.
|
|
201
|
+
if (!s.output && s.solOutRaw !== undefined && s.solOut !== undefined) {
|
|
202
202
|
s.output = {
|
|
203
|
-
asset:
|
|
203
|
+
asset: s.outputAsset,
|
|
204
204
|
raw: String(s.solOutRaw),
|
|
205
205
|
display: s.solOut,
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
|
-
if (!s.realized) {
|
|
209
|
-
s.realized = { asset: s.outputAsset
|
|
208
|
+
if (!s.realized && s.realizedSol !== undefined) {
|
|
209
|
+
s.realized = { asset: s.outputAsset, amount: s.realizedSol };
|
|
210
210
|
}
|
|
211
|
+
delete s.solOut;
|
|
212
|
+
delete s.solOutRaw;
|
|
213
|
+
delete s.realizedSol;
|
|
211
214
|
}
|
|
212
|
-
|
|
213
|
-
if (!p.pnl.realized) {
|
|
215
|
+
if (!p.pnl.realized && p.pnl.realizedSol !== undefined) {
|
|
214
216
|
p.pnl.realized = { asset: p.entry.inputAsset, amount: p.pnl.realizedSol };
|
|
215
217
|
}
|
|
216
|
-
if (!p.pnl.unrealized) {
|
|
218
|
+
if (!p.pnl.unrealized && p.pnl.unrealizedSol !== undefined) {
|
|
217
219
|
p.pnl.unrealized = { asset: p.entry.inputAsset, amount: p.pnl.unrealizedSol };
|
|
218
220
|
}
|
|
221
|
+
// Guarantee canonical pnl shape exists for downstream readers.
|
|
222
|
+
if (!p.pnl.realized)
|
|
223
|
+
p.pnl.realized = { asset: p.entry.inputAsset, amount: 0 };
|
|
224
|
+
if (!p.pnl.unrealized)
|
|
225
|
+
p.pnl.unrealized = { asset: p.entry.inputAsset, amount: 0 };
|
|
226
|
+
delete p.pnl.realizedSol;
|
|
227
|
+
delete p.pnl.unrealizedSol;
|
|
219
228
|
}
|
|
220
229
|
else {
|
|
221
230
|
if (!p.entry.inputAsset)
|
|
@@ -227,24 +236,33 @@ export function normalizePosition(p) {
|
|
|
227
236
|
}
|
|
228
237
|
for (const s of p.sells) {
|
|
229
238
|
if (!s.outputAsset)
|
|
230
|
-
s.outputAsset = p.entry.inputAsset;
|
|
231
|
-
if (!s.output) {
|
|
239
|
+
s.outputAsset = p.entry.inputAsset ?? 'ETH';
|
|
240
|
+
if (!s.output && s.ethOutRawWei !== undefined && s.ethOut !== undefined) {
|
|
232
241
|
s.output = {
|
|
233
|
-
asset: s.outputAsset
|
|
242
|
+
asset: s.outputAsset,
|
|
234
243
|
raw: s.ethOutRawWei,
|
|
235
244
|
display: s.ethOut,
|
|
236
245
|
};
|
|
237
246
|
}
|
|
238
|
-
if (!s.realized) {
|
|
239
|
-
s.realized = { asset: s.outputAsset
|
|
247
|
+
if (!s.realized && s.realizedEth !== undefined) {
|
|
248
|
+
s.realized = { asset: s.outputAsset, amount: s.realizedEth };
|
|
240
249
|
}
|
|
250
|
+
delete s.ethOut;
|
|
251
|
+
delete s.ethOutRawWei;
|
|
252
|
+
delete s.realizedEth;
|
|
241
253
|
}
|
|
242
|
-
if (!p.pnl.realized) {
|
|
254
|
+
if (!p.pnl.realized && p.pnl.realizedEth !== undefined) {
|
|
243
255
|
p.pnl.realized = { asset: p.entry.inputAsset, amount: p.pnl.realizedEth };
|
|
244
256
|
}
|
|
245
|
-
if (!p.pnl.unrealized) {
|
|
257
|
+
if (!p.pnl.unrealized && p.pnl.unrealizedEth !== undefined) {
|
|
246
258
|
p.pnl.unrealized = { asset: p.entry.inputAsset, amount: p.pnl.unrealizedEth };
|
|
247
259
|
}
|
|
260
|
+
if (!p.pnl.realized)
|
|
261
|
+
p.pnl.realized = { asset: p.entry.inputAsset, amount: 0 };
|
|
262
|
+
if (!p.pnl.unrealized)
|
|
263
|
+
p.pnl.unrealized = { asset: p.entry.inputAsset, amount: 0 };
|
|
264
|
+
delete p.pnl.realizedEth;
|
|
265
|
+
delete p.pnl.unrealizedEth;
|
|
248
266
|
}
|
|
249
267
|
return p;
|
|
250
268
|
}
|
|
@@ -857,8 +875,6 @@ export async function buyBase(opts) {
|
|
|
857
875
|
riskFlags: opts.riskFlags ?? [],
|
|
858
876
|
sells: [],
|
|
859
877
|
pnl: {
|
|
860
|
-
realizedEth: 0,
|
|
861
|
-
unrealizedEth: 0,
|
|
862
878
|
unrealizedPct: 0,
|
|
863
879
|
lastPricedAt: null,
|
|
864
880
|
realized: { asset: inputAsset, amount: 0 },
|
|
@@ -868,6 +884,7 @@ export async function buyBase(opts) {
|
|
|
868
884
|
// Dry-run must be strictly read-only: simulated trades never touch live state.
|
|
869
885
|
if (!opts.dryRun)
|
|
870
886
|
writePosition(position);
|
|
887
|
+
const summary = `${opts.dryRun ? '[dry-run] ' : ''}Bought ${tokensOut} ${opts.ca} for ${position.entry.amountIn} on Base.`;
|
|
871
888
|
return {
|
|
872
889
|
positionPath: opts.dryRun
|
|
873
890
|
? `simulated:${opts.ca}`
|
|
@@ -887,6 +904,7 @@ export async function buyBase(opts) {
|
|
|
887
904
|
protectedExec: !!opts.protectedExec,
|
|
888
905
|
rpcUrl,
|
|
889
906
|
inputAsset,
|
|
907
|
+
summary,
|
|
890
908
|
};
|
|
891
909
|
}
|
|
892
910
|
export async function sellBase(opts) {
|
|
@@ -994,7 +1012,7 @@ export async function sellBase(opts) {
|
|
|
994
1012
|
// Entry cost = entry amount + gas fee (ETH-only — fee was paid in ETH at swap time;
|
|
995
1013
|
// for USDC entries we treat gas as small and ignore it in the USDC accounting).
|
|
996
1014
|
const entryRawStr = position.entry.amountInRaw ?? position.entry.amountInRawWei;
|
|
997
|
-
let
|
|
1015
|
+
let realizedAmount;
|
|
998
1016
|
const inputAsset = position.entry.inputAsset ?? 'ETH';
|
|
999
1017
|
if (inputAsset === 'ETH' && outputAsset === 'ETH') {
|
|
1000
1018
|
const entryAmountWei = BigInt(entryRawStr);
|
|
@@ -1004,18 +1022,18 @@ export async function sellBase(opts) {
|
|
|
1004
1022
|
const sellFeeWei = BigInt(swap.feeWei);
|
|
1005
1023
|
const proceedsNetWei = BigInt(outRaw) - sellFeeWei;
|
|
1006
1024
|
const realizedWei = proceedsNetWei - costWei;
|
|
1007
|
-
|
|
1025
|
+
realizedAmount = Number(realizedWei) / 1e18;
|
|
1008
1026
|
}
|
|
1009
1027
|
else if (inputAsset === 'USDC' && outputAsset === 'USDC') {
|
|
1010
1028
|
// USDC in, USDC out — fees were ETH but we don't subtract them from USDC PnL.
|
|
1011
1029
|
const entryUsdc = BigInt(entryRawStr);
|
|
1012
1030
|
const costUsdc = (entryUsdc * tokensToSellRaw) / totalRaw;
|
|
1013
1031
|
const realized6 = BigInt(outRaw) - costUsdc;
|
|
1014
|
-
|
|
1032
|
+
realizedAmount = Number(realized6) / 1e6;
|
|
1015
1033
|
}
|
|
1016
1034
|
else {
|
|
1017
|
-
// Mixed in/out (rare — shouldn't happen with symmetric exit). Best-effort
|
|
1018
|
-
|
|
1035
|
+
// Mixed in/out (rare — shouldn't happen with symmetric exit). Best-effort.
|
|
1036
|
+
realizedAmount = 0;
|
|
1019
1037
|
}
|
|
1020
1038
|
const nowIso = new Date().toISOString();
|
|
1021
1039
|
position.sells.push({
|
|
@@ -1023,32 +1041,25 @@ export async function sellBase(opts) {
|
|
|
1023
1041
|
time: nowIso,
|
|
1024
1042
|
tokensIn: tokensInDisplay,
|
|
1025
1043
|
tokensInRaw: tokensToSellRaw.toString(),
|
|
1026
|
-
ethOut: outDisplay,
|
|
1027
|
-
ethOutRawWei: outRaw,
|
|
1028
1044
|
percentRequested: opts.percent,
|
|
1029
|
-
realizedEth,
|
|
1030
1045
|
reason: opts.reason.trim(),
|
|
1031
1046
|
feeWei: swap.feeWei,
|
|
1032
1047
|
slippageBpsUsed: slippageBps,
|
|
1033
1048
|
protectedExec: !!opts.protectedExec,
|
|
1034
1049
|
outputAsset,
|
|
1035
|
-
// Canonical, asset-tagged accounting — preferred over legacy `ethOut*` fields.
|
|
1036
1050
|
output: { asset: outputAsset, raw: outRaw, display: outDisplay },
|
|
1037
|
-
realized: { asset: outputAsset, amount:
|
|
1051
|
+
realized: { asset: outputAsset, amount: realizedAmount },
|
|
1038
1052
|
});
|
|
1039
|
-
|
|
1040
|
-
position.pnl.realized = { asset: position.entry.inputAsset ?? 'ETH', amount:
|
|
1053
|
+
const totalRealized = position.sells.reduce((a, s) => a + (s.realized?.amount ?? 0), 0);
|
|
1054
|
+
position.pnl.realized = { asset: position.entry.inputAsset ?? 'ETH', amount: totalRealized };
|
|
1041
1055
|
// Position closes when (a) the user explicitly sold 100% — even if the
|
|
1042
1056
|
// on-chain balance was less than the book amount due to drift — or (b) the
|
|
1043
|
-
// sum of recorded sells exceeds the book entry amount.
|
|
1044
|
-
// status='closed', and we account for any drift between book and chain so
|
|
1045
|
-
// partial sells of a drifted balance still close correctly when fully exited.
|
|
1057
|
+
// sum of recorded sells exceeds the book entry amount.
|
|
1046
1058
|
const newSoldRaw = soldRaw + tokensToSellRaw;
|
|
1047
1059
|
const fullyExited = opts.percent >= 100 ||
|
|
1048
1060
|
newSoldRaw + reconcileDriftRaw >= totalRaw;
|
|
1049
1061
|
if (fullyExited) {
|
|
1050
1062
|
position.status = 'closed';
|
|
1051
|
-
position.pnl.unrealizedEth = 0;
|
|
1052
1063
|
position.pnl.unrealizedPct = 0;
|
|
1053
1064
|
position.pnl.unrealized = { asset: position.entry.inputAsset ?? 'ETH', amount: 0 };
|
|
1054
1065
|
}
|
|
@@ -1056,6 +1067,9 @@ export async function sellBase(opts) {
|
|
|
1056
1067
|
// position file (otherwise a simulation can "close" a real position).
|
|
1057
1068
|
if (!opts.dryRun)
|
|
1058
1069
|
writePosition(position);
|
|
1070
|
+
const realizedSign = realizedAmount >= 0 ? '+' : '';
|
|
1071
|
+
const realizedDigits = outputAsset === 'USDC' ? 6 : 8;
|
|
1072
|
+
const summary = `${opts.dryRun ? '[dry-run] ' : ''}Sold ${tokensInDisplay} ${opts.ca} for ${outDisplay}; realized ${realizedSign}${realizedAmount.toFixed(realizedDigits)} ${outputAsset}; position ${position.status}.`;
|
|
1059
1073
|
return {
|
|
1060
1074
|
positionPath: opts.dryRun
|
|
1061
1075
|
? `simulated:${opts.ca}`
|
|
@@ -1063,9 +1077,6 @@ export async function sellBase(opts) {
|
|
|
1063
1077
|
txHash: swap.txHash,
|
|
1064
1078
|
tokensIn: tokensInDisplay,
|
|
1065
1079
|
tokensInRaw: tokensToSellRaw.toString(),
|
|
1066
|
-
ethOut: outDisplay,
|
|
1067
|
-
ethOutRawWei: outRaw,
|
|
1068
|
-
realizedEth,
|
|
1069
1080
|
positionStatus: position.status,
|
|
1070
1081
|
wallet: signer.address,
|
|
1071
1082
|
mint: opts.ca,
|
|
@@ -1078,7 +1089,8 @@ export async function sellBase(opts) {
|
|
|
1078
1089
|
outputAsset,
|
|
1079
1090
|
reconcileDriftRaw: reconcileDriftRaw === 0n ? undefined : reconcileDriftRaw.toString(),
|
|
1080
1091
|
output: { asset: outputAsset, raw: outRaw, display: outDisplay },
|
|
1081
|
-
realized: { asset: outputAsset, amount:
|
|
1092
|
+
realized: { asset: outputAsset, amount: realizedAmount },
|
|
1093
|
+
summary,
|
|
1082
1094
|
};
|
|
1083
1095
|
}
|
|
1084
1096
|
export async function syncBase(opts = {}) {
|
|
@@ -1134,7 +1146,6 @@ export async function syncBase(opts = {}) {
|
|
|
1134
1146
|
unrealizedPct = remainingCost > 0n
|
|
1135
1147
|
? (Number(diff) / Number(remainingCost)) * 100
|
|
1136
1148
|
: 0;
|
|
1137
|
-
p.pnl.unrealizedEth = unrealizedEth;
|
|
1138
1149
|
p.pnl.unrealizedPct = unrealizedPct;
|
|
1139
1150
|
p.pnl.lastPricedAt = nowIso;
|
|
1140
1151
|
p.pnl.unrealized = { asset: p.entry.inputAsset ?? 'ETH', amount: unrealizedEth };
|
|
@@ -1144,7 +1155,6 @@ export async function syncBase(opts = {}) {
|
|
|
1144
1155
|
}
|
|
1145
1156
|
}
|
|
1146
1157
|
else {
|
|
1147
|
-
p.pnl.unrealizedEth = 0;
|
|
1148
1158
|
p.pnl.unrealizedPct = 0;
|
|
1149
1159
|
p.pnl.unrealized = { asset: p.entry.inputAsset ?? 'ETH', amount: 0 };
|
|
1150
1160
|
}
|
|
@@ -1303,8 +1313,6 @@ export async function buy(opts) {
|
|
|
1303
1313
|
riskFlags: opts.riskFlags ?? [],
|
|
1304
1314
|
sells: [],
|
|
1305
1315
|
pnl: {
|
|
1306
|
-
realizedSol: 0,
|
|
1307
|
-
unrealizedSol: 0,
|
|
1308
1316
|
unrealizedPct: 0,
|
|
1309
1317
|
lastPricedAt: null,
|
|
1310
1318
|
realized: { asset: inputAsset, amount: 0 },
|
|
@@ -1356,6 +1364,7 @@ export async function buy(opts) {
|
|
|
1356
1364
|
protectedExec: !!opts.protectedExec,
|
|
1357
1365
|
forensics,
|
|
1358
1366
|
inputAsset,
|
|
1367
|
+
summary: `${opts.dryRun ? '[dry-run] ' : ''}Bought ${tokensOut} ${opts.ca} for ${position.entry.amountIn} on Solana.`,
|
|
1359
1368
|
};
|
|
1360
1369
|
}
|
|
1361
1370
|
export async function sell(opts) {
|
|
@@ -1468,10 +1477,7 @@ export async function sell(opts) {
|
|
|
1468
1477
|
time: nowIso,
|
|
1469
1478
|
tokensIn: tokensInDisplay,
|
|
1470
1479
|
tokensInRaw: tokensToSellRaw.toString(),
|
|
1471
|
-
solOut: solOutDisplay,
|
|
1472
|
-
solOutRaw,
|
|
1473
1480
|
percentRequested: opts.percent,
|
|
1474
|
-
realizedSol,
|
|
1475
1481
|
reason: opts.reason.trim(),
|
|
1476
1482
|
feeLamports,
|
|
1477
1483
|
tipLamports,
|
|
@@ -1479,16 +1485,14 @@ export async function sell(opts) {
|
|
|
1479
1485
|
protectedExec: !!opts.protectedExec,
|
|
1480
1486
|
forensics,
|
|
1481
1487
|
outputAsset,
|
|
1482
|
-
// Canonical, asset-tagged accounting — preferred over legacy `solOut*` fields.
|
|
1483
1488
|
output: { asset: outputAsset, raw: String(solOutRaw), display: solOutDisplay },
|
|
1484
1489
|
realized: { asset: outputAsset, amount: realizedSol },
|
|
1485
1490
|
});
|
|
1486
|
-
|
|
1487
|
-
position.pnl.realized = { asset: position.entry.inputAsset ?? 'SOL', amount:
|
|
1491
|
+
const totalRealized = position.sells.reduce((a, s) => a + (s.realized?.amount ?? 0), 0);
|
|
1492
|
+
position.pnl.realized = { asset: position.entry.inputAsset ?? 'SOL', amount: totalRealized };
|
|
1488
1493
|
const newSoldRaw = soldRaw + tokensToSellRaw;
|
|
1489
1494
|
if (newSoldRaw >= totalRaw) {
|
|
1490
1495
|
position.status = 'closed';
|
|
1491
|
-
position.pnl.unrealizedSol = 0;
|
|
1492
1496
|
position.pnl.unrealizedPct = 0;
|
|
1493
1497
|
position.pnl.unrealized = { asset: position.entry.inputAsset ?? 'SOL', amount: 0 };
|
|
1494
1498
|
}
|
|
@@ -1514,6 +1518,9 @@ export async function sell(opts) {
|
|
|
1514
1518
|
forensics,
|
|
1515
1519
|
});
|
|
1516
1520
|
}
|
|
1521
|
+
const sign = realizedSol >= 0 ? '+' : '';
|
|
1522
|
+
const digits = outputAsset === 'USDC' ? 6 : 8;
|
|
1523
|
+
const summary = `${opts.dryRun ? '[dry-run] ' : ''}Sold ${tokensInDisplay} ${opts.ca} for ${solOutDisplay}; realized ${sign}${realizedSol.toFixed(digits)} ${outputAsset}; position ${position.status}.`;
|
|
1517
1524
|
return {
|
|
1518
1525
|
positionPath: opts.dryRun
|
|
1519
1526
|
? `simulated:${opts.ca}`
|
|
@@ -1521,9 +1528,6 @@ export async function sell(opts) {
|
|
|
1521
1528
|
txSignature: swap.txSignature,
|
|
1522
1529
|
tokensIn: tokensInDisplay,
|
|
1523
1530
|
tokensInRaw: tokensToSellRaw.toString(),
|
|
1524
|
-
solOut: solOutDisplay,
|
|
1525
|
-
solOutRaw,
|
|
1526
|
-
realizedSol,
|
|
1527
1531
|
positionStatus: position.status,
|
|
1528
1532
|
wallet: signer.address,
|
|
1529
1533
|
mint: opts.ca,
|
|
@@ -1537,6 +1541,7 @@ export async function sell(opts) {
|
|
|
1537
1541
|
outputAsset,
|
|
1538
1542
|
output: { asset: outputAsset, raw: String(solOutRaw), display: solOutDisplay },
|
|
1539
1543
|
realized: { asset: outputAsset, amount: realizedSol },
|
|
1544
|
+
summary,
|
|
1540
1545
|
};
|
|
1541
1546
|
}
|
|
1542
1547
|
export async function sync(opts = {}) {
|
|
@@ -1594,7 +1599,6 @@ export async function sync(opts = {}) {
|
|
|
1594
1599
|
unrealizedSol = usdcOut - remCost; // field name kept; holds USDC realized for USDC positions
|
|
1595
1600
|
unrealizedPct = remCost > 0 ? (unrealizedSol / remCost) * 100 : 0;
|
|
1596
1601
|
}
|
|
1597
|
-
p.pnl.unrealizedSol = unrealizedSol;
|
|
1598
1602
|
p.pnl.unrealizedPct = unrealizedPct;
|
|
1599
1603
|
p.pnl.lastPricedAt = nowIso;
|
|
1600
1604
|
p.pnl.unrealized = { asset: p.entry.inputAsset ?? 'SOL', amount: unrealizedSol };
|
|
@@ -1604,7 +1608,6 @@ export async function sync(opts = {}) {
|
|
|
1604
1608
|
}
|
|
1605
1609
|
}
|
|
1606
1610
|
else {
|
|
1607
|
-
p.pnl.unrealizedSol = 0;
|
|
1608
1611
|
p.pnl.unrealizedPct = 0;
|
|
1609
1612
|
p.pnl.unrealized = { asset: p.entry.inputAsset ?? 'SOL', amount: 0 };
|
|
1610
1613
|
}
|
|
@@ -1793,27 +1796,31 @@ export async function computePnl(opts = {}) {
|
|
|
1793
1796
|
let usdcRealized = 0, usdcUnrealized = 0, usdcCount = 0;
|
|
1794
1797
|
for (const p of solanaPositions) {
|
|
1795
1798
|
const asset = p.entry.inputAsset ?? 'SOL';
|
|
1799
|
+
const realized = p.pnl.realized?.amount ?? 0;
|
|
1800
|
+
const unrealized = p.pnl.unrealized?.amount ?? 0;
|
|
1796
1801
|
if (asset === 'USDC') {
|
|
1797
|
-
usdcRealized +=
|
|
1798
|
-
usdcUnrealized +=
|
|
1802
|
+
usdcRealized += realized;
|
|
1803
|
+
usdcUnrealized += unrealized;
|
|
1799
1804
|
usdcCount += 1;
|
|
1800
1805
|
}
|
|
1801
1806
|
else {
|
|
1802
|
-
solRealized +=
|
|
1803
|
-
solUnrealized +=
|
|
1807
|
+
solRealized += realized;
|
|
1808
|
+
solUnrealized += unrealized;
|
|
1804
1809
|
solCount += 1;
|
|
1805
1810
|
}
|
|
1806
1811
|
}
|
|
1807
1812
|
for (const p of basePositions) {
|
|
1808
1813
|
const asset = p.entry.inputAsset ?? 'ETH';
|
|
1814
|
+
const realized = p.pnl.realized?.amount ?? 0;
|
|
1815
|
+
const unrealized = p.pnl.unrealized?.amount ?? 0;
|
|
1809
1816
|
if (asset === 'USDC') {
|
|
1810
|
-
usdcRealized +=
|
|
1811
|
-
usdcUnrealized +=
|
|
1817
|
+
usdcRealized += realized;
|
|
1818
|
+
usdcUnrealized += unrealized;
|
|
1812
1819
|
usdcCount += 1;
|
|
1813
1820
|
}
|
|
1814
1821
|
else {
|
|
1815
|
-
ethRealized +=
|
|
1816
|
-
ethUnrealized +=
|
|
1822
|
+
ethRealized += realized;
|
|
1823
|
+
ethUnrealized += unrealized;
|
|
1817
1824
|
ethCount += 1;
|
|
1818
1825
|
}
|
|
1819
1826
|
}
|
|
@@ -1863,8 +1870,8 @@ export async function computePnl(opts = {}) {
|
|
|
1863
1870
|
entry = { key: displayKey, realized: 0, unrealized: 0, count: 0, unit: asset, chain: 'solana' };
|
|
1864
1871
|
map.set(key, entry);
|
|
1865
1872
|
}
|
|
1866
|
-
entry.realized += p.pnl.
|
|
1867
|
-
entry.unrealized += p.pnl.
|
|
1873
|
+
entry.realized += p.pnl.realized?.amount ?? 0;
|
|
1874
|
+
entry.unrealized += p.pnl.unrealized?.amount ?? 0;
|
|
1868
1875
|
entry.count += 1;
|
|
1869
1876
|
}
|
|
1870
1877
|
for (const p of basePositions) {
|
|
@@ -1876,8 +1883,8 @@ export async function computePnl(opts = {}) {
|
|
|
1876
1883
|
entry = { key: displayKey, realized: 0, unrealized: 0, count: 0, unit: asset, chain: 'base' };
|
|
1877
1884
|
map.set(key, entry);
|
|
1878
1885
|
}
|
|
1879
|
-
entry.realized += p.pnl.
|
|
1880
|
-
entry.unrealized += p.pnl.
|
|
1886
|
+
entry.realized += p.pnl.realized?.amount ?? 0;
|
|
1887
|
+
entry.unrealized += p.pnl.unrealized?.amount ?? 0;
|
|
1881
1888
|
entry.count += 1;
|
|
1882
1889
|
}
|
|
1883
1890
|
byGroup = Array.from(map.values());
|