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 +10 -0
- package/dist/cli.cjs +128 -0
- package/dist/cli.cjs.map +1 -1
- package/package.json +1 -1
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")
|