Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions plugins/ton-trading-bot/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,14 +631,16 @@ export const tools = (sdk) => [
// If simulation trade was opened with TON, credit back principal + profit/loss in TON.
// The balance was decremented by amount_in (TON) on open; on close we restore it.
// When USD prices are available the pnl is in USD — convert back to TON using
// the entry price (USD price of TON at trade open). When no prices were provided
// (same-asset TON→TON trade) the raw amount_out is already in TON.
// the exit price (USD price of TON at trade close). Using the exit price correctly
// reflects how much TON the USD profit buys at the current market rate.
// Example: pnl=$1.995 at exit $1.393/TON → 1.432 TON added (not 1.583 with entry $1.26).
// When no prices were provided (same-asset TON→TON trade) the raw amount_out is in TON.
if (entry.mode === "simulation" && entry.from_asset === "TON") {
const simBalance = getSimBalance(sdk);
const entryTonPriceUsd = entry.entry_price_usd ?? null;
const exitTonPriceUsd = exit_price_usd ?? entry.entry_price_usd ?? null;
const creditTon =
entryTonPriceUsd != null
? entry.amount_in + pnl / entryTonPriceUsd
exitTonPriceUsd != null
? entry.amount_in + pnl / exitTonPriceUsd
: amount_out; // same-currency (TON→TON): just return what came out
setSimBalance(sdk, simBalance + creditTon);
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/ton-trading-bot/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "ton-trading-bot",
"name": "TON Trading Bot",
"version": "2.0.0",
"version": "2.0.1",
"description": "Atomic TON trading tools: market data, portfolio, risk validation, simulation, DEX swap execution, cross-DEX arbitrage, sniper trading, copy trading, liquidity pools, farming, backtesting, risk management, and automation.",
"author": {
"name": "xlabtg",
Expand Down
78 changes: 73 additions & 5 deletions plugins/ton-trading-bot/tests/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1537,7 +1537,7 @@ describe("ton-trading-bot plugin", () => {
// 13 TON → USDT trade; entry_price_usd=1.33 (TON at open), exit_price_usd=1.34 (TON rose slightly)
// Both prices refer to from_asset (TON) per the corrected semantics.
// pnl_usd = amount_in * (exit_price - entry_price) = 13 * (1.34 - 1.33) = 13 * 0.01 = 0.13 USD
// credit_ton = amount_in + pnl_usd / entry_price_usd = 13 + 0.13/1.33 ≈ 13.098
// credit_ton = amount_in + pnl_usd / exit_price_usd = 13 + 0.13/1.34 ≈ 13.097
const openTrade = {
id: 100,
mode: "simulation",
Expand Down Expand Up @@ -1575,7 +1575,7 @@ describe("ton-trading-bot plugin", () => {
);
assert.equal(result.success, true);
assert.ok(savedBalance !== null, "simulation balance should be updated on close");
// Expected: 50 (prev balance) + 13 (principal) + 0.13/1.33 ≈ 63.098
// Expected: 50 (prev balance) + 13 (principal) + 0.13/1.34 ≈ 63.097
assert.ok(savedBalance > 63, `balance should be restored to ~63+, got ${savedBalance}`);
assert.ok(savedBalance < 64, `balance should be around 63, got ${savedBalance}`);
});
Expand Down Expand Up @@ -1663,7 +1663,7 @@ describe("ton-trading-bot plugin", () => {
// 10 TON → USDT at $2/TON (entry), TON dropped to $1.5 at exit → loss
// exit_price_usd = 1.5 (TON exit price, from_asset price at close)
// usdOut = 10 * 1.5 = 15, usdIn = 10 * 2 = 20, pnl = -5 USD
// credit_ton = 10 + (-5/2) = 10 - 2.5 = 7.5 TON
// credit_ton = 10 + (-5/1.5) = 10 - 3.333 = 6.667 TON (uses exit_price for conversion)
const openTrade = {
id: 103,
mode: "simulation",
Expand Down Expand Up @@ -1701,8 +1701,76 @@ describe("ton-trading-bot plugin", () => {
);
assert.equal(result.success, true);
assert.equal(result.data.profit_or_loss, "loss");
// credit_ton = 7.5, new balance = 90 + 7.5 = 97.5
assert.ok(Math.abs(savedBalance - 97.5) < 0.0001, `expected 97.5, got ${savedBalance}`);
// credit_ton = 10 + (-5/1.5) = 10 - 3.333 = 6.667, new balance = 90 + 6.667 = 96.667
const expectedBalance = 90 + 10 + (-5 / 1.5); // 96.667
assert.ok(Math.abs(savedBalance - expectedBalance) < 0.0001, `expected ${expectedBalance}, got ${savedBalance}`);
});

it("credits back correct profit for large price move (issue #133): uses exit_price not entry_price", async () => {
// Issue #133: 15 TON trade at entry $1.26, TON rises to $1.393 (+10.6%)
// Expected profit in sim balance: ~1.43 TON (pnl converted at exit price)
// Buggy behaviour: used entry_price for conversion → profit = 1.583 TON (over-credited by 10.6%)
//
// pnl_usd = 15 * (1.393 - 1.26) = 15 * 0.133 = 1.995 USD
// credit_ton (correct, exit price): 15 + 1.995/1.393 = 16.432 TON → profit = 1.432 TON ≈ 1.43
// credit_ton (buggy, entry price): 15 + 1.995/1.26 = 16.583 TON → profit = 1.583 TON (too high)
const openTrade = {
id: 133,
mode: "simulation",
from_asset: "TON",
to_asset: "EQUsdtAddress",
amount_in: 15,
entry_price_usd: 1.26,
amount_out: null,
status: "open",
};
let savedBalance = null;
const sdk = {
...makeSdk({ dbRows: { trade: openTrade, simBalance: { balance: 1000 } } }),
db: {
exec: () => {},
prepare: (sql) => ({
get: () => {
if (sql.includes("sim_balance")) return { balance: 1000 };
if (sql.includes("trade_journal") && sql.includes("WHERE id")) return openTrade;
return null;
},
all: () => [],
run: (...args) => {
if (sql.includes("INSERT INTO sim_balance")) savedBalance = args[1];
return { lastInsertRowid: 1 };
},
}),
},
log: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} },
};
const tool = mod.tools(sdk).find((t) => t.name === "ton_trading_record_trade");
const result = await tool.execute(
{ trade_id: 133, amount_out: 20.895, exit_price_usd: 1.393 },
makeContext()
);
assert.equal(result.success, true);
// pnl = 15 * 1.393 - 15 * 1.26 = 1.995 USD
assert.ok(Math.abs(result.data.pnl - 1.995) < 0.001, `pnl should be ~1.995, got ${result.data.pnl}`);

// Correct: credit uses exit_price ($1.393) → balance = 1000 + 15 + 1.995/1.393 = 1016.432
const pnlUsd = 15 * (1.393 - 1.26);
const expectedCredit = 15 + pnlUsd / 1.393; // = 16.432 (uses exit price)
const buggyCredit = 15 + pnlUsd / 1.26; // = 16.583 (uses entry price — old bug)
const expectedBalance = 1000 + expectedCredit; // = 1016.432
const expectedProfit = expectedCredit - 15; // = 1.432 TON ≈ expected "~1.43 TON"

assert.ok(savedBalance !== null, "simulation balance should be updated on close");
assert.ok(
Math.abs(savedBalance - expectedBalance) < 0.001,
`balance should be ~${expectedBalance.toFixed(3)} (profit ~${expectedProfit.toFixed(3)} TON), got ${savedBalance}`
);
// Verify it is NOT the over-credited (buggy) value
const buggyBalance = 1000 + buggyCredit;
assert.ok(
Math.abs(savedBalance - buggyBalance) > 0.05,
`balance must NOT be the over-credited buggy value ~${buggyBalance.toFixed(3)}, got ${savedBalance}`
);
});
});

Expand Down
Loading