The entry price is always recalculated when adding or reducing trade size. This behavior can lead to inaccurate profit and loss calculations, resulting in unfair financial outcomes for traders.
The entry price is very important because it is a reference point for when the trader started the trade and it is used for calculating the profit or loss for both long and short positions. In the _fillOrder
, there are only 2 options, either the position is cleared when a trader is closing the trader or the position is updated, this update changes the entry price.
The price been updated is the current price not the entry price of the trader.
function fillMarketOrder(
uint128 tradingAccountId,
uint128 marketId,
bytes calldata priceData
)
external
onlyMarketOrderKeeper(marketId)
{
FillMarketOrder_Context memory ctx;
SettlementConfiguration.Data storage settlementConfiguration =
SettlementConfiguration.load(marketId, SettlementConfiguration.MARKET_ORDER_CONFIGURATION_ID);
MarketOrder.Data storage marketOrder = MarketOrder.loadExisting(tradingAccountId);
if (marketId != marketOrder.marketId) {
revert Errors.OrderMarketIdMismatch(marketId, marketOrder.marketId);
}
PerpMarket.Data storage perpMarket = PerpMarket.load(marketId);
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
(ctx.bidX18, ctx.askX18) =
settlementConfiguration.verifyOffchainPrice(priceData, globalConfiguration.maxVerificationDelay);
ctx.sizeDeltaX18 = sd59x18(marketOrder.sizeDelta);
ctx.isBuyOrder = ctx.sizeDeltaX18.gt(SD59x18_ZERO);
ctx.indexPriceX18 = ctx.isBuyOrder ? ctx.askX18 : ctx.bidX18;
@> ctx.fillPriceX18 = perpMarket.getMarkPrice(ctx.sizeDeltaX18, ctx.indexPriceX18);
_fillOrder(
tradingAccountId,
marketId,
SettlementConfiguration.MARKET_ORDER_CONFIGURATION_ID,
ctx.sizeDeltaX18,
@> ctx.fillPriceX18
);
marketOrder.clear();
}
function _fillOrder(
uint128 tradingAccountId,
uint128 marketId,
uint128 settlementConfigurationId,
SD59x18 sizeDeltaX18,
UD60x18 fillPriceX18
)
internal
virtual
{
FillOrderContext memory ctx;
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
SettlementConfiguration.Data storage settlementConfiguration =
SettlementConfiguration.load(marketId, settlementConfigurationId);
ctx.usdToken = globalConfiguration.usdToken;
ctx.isIncreasing = Position.isIncreasing(tradingAccountId, marketId, sizeDeltaX18.intoInt256().toInt128());
if (ctx.isIncreasing) {
globalConfiguration.checkMarketIsEnabled(marketId);
settlementConfiguration.checkIsSettlementEnabled();
}
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
PerpMarket.Data storage perpMarket = PerpMarket.load(marketId);
perpMarket.checkTradeSize(sizeDeltaX18);
ctx.fundingRateX18 = perpMarket.getCurrentFundingRate();
ctx.fundingFeePerUnitX18 = perpMarket.getNextFundingFeePerUnit(ctx.fundingRateX18, fillPriceX18);
perpMarket.updateFunding(ctx.fundingRateX18, ctx.fundingFeePerUnitX18);
ctx.orderFeeUsdX18 = perpMarket.getOrderFeeUsd(sizeDeltaX18, fillPriceX18);
ctx.settlementFeeUsdX18 = ud60x18(uint256(settlementConfiguration.fee));
Position.Data storage oldPosition = Position.load(tradingAccountId, marketId);
ctx.oldPositionSizeX18 = sd59x18(oldPosition.size);
{
(
UD60x18 requiredInitialMarginUsdX18,
UD60x18 requiredMaintenanceMarginUsdX18,
SD59x18 accountTotalUnrealizedPnlUsdX18
) = tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(marketId, sizeDeltaX18);
ctx.shouldUseMaintenanceMargin = !ctx.isIncreasing && !ctx.oldPositionSizeX18.isZero();
ctx.requiredMarginUsdX18 =
ctx.shouldUseMaintenanceMargin ? requiredMaintenanceMarginUsdX18 : requiredInitialMarginUsdX18;
tradingAccount.validateMarginRequirement(
ctx.requiredMarginUsdX18,
tradingAccount.getMarginBalanceUsd(accountTotalUnrealizedPnlUsdX18),
ctx.orderFeeUsdX18.add(ctx.settlementFeeUsdX18)
);
}
ctx.pnlUsdX18 =
oldPosition.getUnrealizedPnl(fillPriceX18).add(oldPosition.getAccruedFunding(ctx.fundingFeePerUnitX18));
ctx.newPosition = Position.Data({
size: ctx.oldPositionSizeX18.add(sizeDeltaX18).intoInt256(),
lastInteractionPrice: fillPriceX18.intoUint128(),
lastInteractionFundingFeePerUnit: ctx.fundingFeePerUnitX18.intoInt256().toInt128()
});
ctx.newPositionSizeX18 = sd59x18(ctx.newPosition.size);
(ctx.newOpenInterestX18, ctx.newSkewX18) =
perpMarket.checkOpenInterestLimits(sizeDeltaX18, ctx.oldPositionSizeX18, ctx.newPositionSizeX18);
perpMarket.updateOpenInterest(ctx.newOpenInterestX18, ctx.newSkewX18);
tradingAccount.updateActiveMarkets(marketId, ctx.oldPositionSizeX18, ctx.newPositionSizeX18);
if (ctx.newPositionSizeX18.isZero()) {
oldPosition.clear();
}
else {
if (ctx.newPositionSizeX18.abs().lt(sd59x18(int256(uint256(perpMarket.configuration.minTradeSizeX18))))) {
revert Errors.NewPositionSizeTooSmall();
}
@> oldPosition.update(ctx.newPosition);
}
if (ctx.pnlUsdX18.gt(SD59x18_ZERO)) {
ctx.marginToAddX18 = ctx.pnlUsdX18.intoUD60x18();
tradingAccount.deposit(ctx.usdToken, ctx.marginToAddX18);
LimitedMintingERC20(ctx.usdToken).mint(address(this), ctx.marginToAddX18.intoUint256());
}
tradingAccount.deductAccountMargin({
feeRecipients: FeeRecipients.Data({
marginCollateralRecipient: globalConfiguration.marginCollateralRecipient,
orderFeeRecipient: globalConfiguration.orderFeeRecipient,
settlementFeeRecipient: globalConfiguration.settlementFeeRecipient
}),
pnlUsdX18: ctx.pnlUsdX18.lt(SD59x18_ZERO) ? ctx.pnlUsdX18.abs().intoUD60x18() : UD60x18_ZERO,
orderFeeUsdX18: ctx.orderFeeUsdX18,
settlementFeeUsdX18: ctx.settlementFeeUsdX18
});
emit LogFillOrder(
msg.sender,
tradingAccountId,
marketId,
sizeDeltaX18.intoInt256(),
fillPriceX18.intoUint256(),
ctx.orderFeeUsdX18.intoUint256(),
ctx.settlementFeeUsdX18.intoUint256(),
ctx.pnlUsdX18.intoInt256(),
ctx.fundingFeePerUnitX18.intoInt256()
);
}