dipcoin-cli 1.3.1 → 1.3.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/README.md CHANGED
@@ -181,6 +181,16 @@ dipcoin-cli position tpsl <symbol> --side <buy|sell> --quantity <q> --leverage <
181
181
  dipcoin-cli position tpsl <symbol> --side sell --quantity 0.01 --leverage 10 \
182
182
  --tp-trigger 105000 --tp-type limit --tp-price 105000
183
183
 
184
+ # Close positions — CLI figures out side & qty
185
+ dipcoin-cli position close BTC # close 100% of BTC position
186
+ dipcoin-cli position close BTC --percent 50 # close half
187
+ dipcoin-cli position close-all # close every open position
188
+ dipcoin-cli position close-all --percent 50 # halve every open position
189
+
190
+ # Manual form is still supported (specify exact qty yourself):
191
+ dipcoin-cli trade sell BTC 0 10x --qty 0.01 --reduce-only # close 0.01 of a long
192
+ dipcoin-cli trade buy ETH 0 5x --qty 1 --reduce-only # close 1 of a short
193
+
184
194
  # Margin operations (on-chain) — auto-prepends a fresh signed price update
185
195
  dipcoin-cli position margin add <symbol> <amount>
186
196
  dipcoin-cli position margin remove <symbol> <amount>
package/dist/cli.cjs CHANGED
@@ -20092,6 +20092,134 @@ function registerPositionCommands(program) {
20092
20092
  handleError(e);
20093
20093
  }
20094
20094
  });
20095
+ // Helper: align a human-readable quantity to the market's step size.
20096
+ async function alignToStepSize(sdk, symbol, qty) {
20097
+ let stepSize = 0.001;
20098
+ try {
20099
+ const pairResult = await sdk.getTradingPairs();
20100
+ if (pairResult.status && pairResult.data) {
20101
+ const pair = pairResult.data.find((p) => p.symbol === symbol);
20102
+ const raw = pair?.stepSize ?? pair?.minTradeQty;
20103
+ if (raw) {
20104
+ const parsed = parseFloat(raw);
20105
+ if (parsed > 0)
20106
+ stepSize = parsed > 1e10 ? parsed / 1e18 : parsed;
20107
+ }
20108
+ }
20109
+ }
20110
+ catch {
20111
+ // fall through with default stepSize
20112
+ }
20113
+ const stepped = Math.floor(qty / stepSize) * stepSize;
20114
+ const decimals = stepSize < 1 ? Math.ceil(-Math.log10(stepSize)) : 0;
20115
+ return parseFloat(stepped.toFixed(decimals));
20116
+ }
20117
+ // Helper: close one position with a reduce-only market order. Returns the API response.
20118
+ async function closeOnePosition(sdk, pos, percent, vault) {
20119
+ const symbol = pos.symbol;
20120
+ const sideStr = String(pos.side).toUpperCase();
20121
+ const closingSide = sideStr === "BUY" ? OrderSide.SELL : OrderSide.BUY;
20122
+ const totalQtyHuman = Number(pos.quantity) / 1e18;
20123
+ const wanted = totalQtyHuman * (percent / 100);
20124
+ const aligned = await alignToStepSize(sdk, symbol, wanted);
20125
+ if (aligned <= 0) {
20126
+ return {
20127
+ ok: false,
20128
+ message: `Closing ${percent}% of ${totalQtyHuman} ${symbol} would round to 0 at market step size — try a larger percentage.`,
20129
+ };
20130
+ }
20131
+ const perpId = await sdk.getPerpetualID(symbol);
20132
+ if (!perpId)
20133
+ return { ok: false, message: `PerpetualID not found for ${symbol}` };
20134
+ const leverageHuman = String(Number(pos.leverage) / 1e18);
20135
+ const params = {
20136
+ symbol,
20137
+ market: perpId,
20138
+ side: closingSide,
20139
+ orderType: OrderType.MARKET,
20140
+ quantity: String(aligned),
20141
+ leverage: leverageHuman,
20142
+ reduceOnly: true,
20143
+ };
20144
+ if (vault)
20145
+ params.creator = vault;
20146
+ const result = await sdk.placeOrder(params);
20147
+ if (!result.status)
20148
+ return { ok: false, message: result.error || "placeOrder failed" };
20149
+ return {
20150
+ ok: true,
20151
+ message: `Closed ${aligned} ${symbol} (${closingSide} reduce-only).`,
20152
+ data: result.data,
20153
+ };
20154
+ }
20155
+ position
20156
+ .command("close")
20157
+ .description("Close a position with a reduce-only market order (default: 100%)")
20158
+ .argument("<symbol>", "Trading pair (e.g. BTC or BTC-PERP)")
20159
+ .option("--percent <n>", "Percent of position to close (1-100, default: 100)", "100")
20160
+ .option("--vault <address>", "Target a vault's position instead of personal")
20161
+ .action(async (symbol, opts) => {
20162
+ try {
20163
+ symbol = normalizeSymbol(symbol);
20164
+ const percent = Number(opts.percent);
20165
+ if (!Number.isFinite(percent) || percent <= 0 || percent > 100) {
20166
+ return handleError(`--percent must be in (0, 100], got ${opts.percent}`);
20167
+ }
20168
+ const sdk = getSDK();
20169
+ const parentAddress = opts.vault || sdk.address;
20170
+ const result = await sdk.getPositions({ parentAddress, symbol });
20171
+ if (!result.status)
20172
+ return handleError(result.error);
20173
+ const pos = (result.data || []).find((p) => p.symbol === symbol);
20174
+ if (!pos)
20175
+ return handleError(`No open position for ${symbol}.`);
20176
+ const r = await closeOnePosition(sdk, pos, percent, opts.vault);
20177
+ if (!r.ok)
20178
+ return handleError(r.message);
20179
+ if (isJson(program))
20180
+ return printJson(r.data);
20181
+ console.log(r.message);
20182
+ }
20183
+ catch (e) {
20184
+ handleError(e);
20185
+ }
20186
+ });
20187
+ position
20188
+ .command("close-all")
20189
+ .description("Close every open position with reduce-only market orders")
20190
+ .option("--percent <n>", "Percent of each position to close (1-100, default: 100)", "100")
20191
+ .option("--vault <address>", "Target vault positions instead of personal")
20192
+ .action(async (opts) => {
20193
+ try {
20194
+ const percent = Number(opts.percent);
20195
+ if (!Number.isFinite(percent) || percent <= 0 || percent > 100) {
20196
+ return handleError(`--percent must be in (0, 100], got ${opts.percent}`);
20197
+ }
20198
+ const sdk = getSDK();
20199
+ const parentAddress = opts.vault || sdk.address;
20200
+ const result = await sdk.getPositions({ parentAddress });
20201
+ if (!result.status)
20202
+ return handleError(result.error);
20203
+ const positions = result.data || [];
20204
+ if (!positions.length) {
20205
+ if (isJson(program))
20206
+ return printJson({ closed: 0, results: [] });
20207
+ return console.log("No open positions to close.");
20208
+ }
20209
+ const results = [];
20210
+ for (const pos of positions) {
20211
+ const r = await closeOnePosition(sdk, pos, percent, opts.vault);
20212
+ results.push({ symbol: pos.symbol, ok: r.ok, message: r.message });
20213
+ if (!isJson(program))
20214
+ console.log(` ${pos.symbol}: ${r.ok ? "OK" : "FAILED"} — ${r.message}`);
20215
+ }
20216
+ if (isJson(program))
20217
+ printJson({ closed: results.filter((r) => r.ok).length, results });
20218
+ }
20219
+ catch (e) {
20220
+ handleError(e);
20221
+ }
20222
+ });
20095
20223
  position
20096
20224
  .command("tpsl")
20097
20225
  .description("Place TP/SL orders on a position")