Several Fixes
This commit is contained in:
1 parent
c184cbfd4f
commit
d9599a9716
3 files changed
+113
-97
No files matched your search
@@ -33,48 +33,54 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
}
|
||||
|
||||
|
||||
async function actualSizeCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
When we submit the order to the exchange, we do it specifying the order size
|
||||
in Base Asset. It is mandatory to be in Base Asset as per CCXT Library API.
|
||||
in Base Asset. It is mandatory to be in Base Asset as per CCXT Library API and
|
||||
probably as per the Exchange API too.
|
||||
|
||||
The exchange might take that size or might change it a little
|
||||
bit. For example, if it does not accept as many decimals as we are sending.
|
||||
If we identify the situation in which the size accepted is different than the
|
||||
size we have accounted for, we need to make several adjustments so that the
|
||||
|
||||
We have also seen that for Market Orders, the exchange usually does not take
|
||||
the size requested exactly. Sometimes is a little bit less or even a little bit
|
||||
more. For Limit orders it is usually the same.
|
||||
|
||||
If it happens that the size accepted (Actual Size) is different than the
|
||||
size requested (Size), we need to make several adjustments so that the
|
||||
accounting syncronizes with reality.
|
||||
*/
|
||||
|
||||
/* This calculation only happens once, the first time the order is checked. */
|
||||
/* This calculation needs to happen only once, the first time the order is checked. */
|
||||
if (tradingEngineOrder.orderBaseAsset.actualSize.value !== tradingEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
|
||||
|
||||
/* We receive the actual size from the exchange at the order.amount field. */
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = order.amount
|
||||
|
||||
/*
|
||||
If not, we will calculate the Quoted Asset Actual Size too. Note that the amount received
|
||||
is in Base Asset only. In the next formula, we are unsing the rate of the order.
|
||||
This rate might be replaced afteerwards for an actual rate. When that happens we will
|
||||
need to adjust these results.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.rate.value
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.actualSize.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
|
||||
/*
|
||||
If the exchange accepted the size sent on the request to create the order,
|
||||
we don't need to do anything else here.
|
||||
*/
|
||||
if (tradingEngineOrder.orderBaseAsset.size.value === tradingEngineOrder.orderBaseAsset.actualSize.value) { return }
|
||||
|
||||
recalculateActualSize()
|
||||
recalculateSizePlaced()
|
||||
|
||||
function recalculateSizePlaced() {
|
||||
function recalculateActualSize() {
|
||||
/*
|
||||
Wee also need to unaccount the previous size placed and correctly account
|
||||
for the the new actual sizes we now have.
|
||||
We will recalculate the Quoted Asset Actual Size. This will also give to it an initial value the first time we do this.
|
||||
In the next formula, we are unsing the rate of the order because we dont know yet the Actual Rate.
|
||||
This rate might be replaced afteerwards for the Actual Rate when this is calculated again once
|
||||
the Actual Rate is known.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.rate.value
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.actualSize.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
}
|
||||
|
||||
function recalculateSizePlaced() {
|
||||
/*
|
||||
We will also recalculate the Size Placed that was previously calculated with the order Size,
|
||||
and now we will do it with the order Actual Size.
|
||||
|
||||
We need to unaccount the previous size placed and correctly account
|
||||
for the the new actual sizes we now know.
|
||||
*/
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value -
|
||||
@@ -104,7 +110,7 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
For example we might have sent a rate with too many decimals and the exchange truncate
|
||||
them. Another thing that might happen is that we send a rate that is matching orders already
|
||||
at the order book. In this case we would get an actual rate not only different than the one
|
||||
we sent, but also, better since the trading engine inside the exchange would match out orders
|
||||
we sent, but also, better since the trading engine inside the exchange would match orders
|
||||
with the first orders best by price.
|
||||
|
||||
In the case of Market Orders we don't even send a rate. For all those reason
|
||||
@@ -112,12 +118,13 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
*/
|
||||
if (order.average !== undefined) {
|
||||
/*
|
||||
We will use the average whenever is available.
|
||||
We will use the average whenever is available. As of today it is not 100% clear when this is
|
||||
available, but it seems sometimes it is not. In those cases we use the price.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = order.average
|
||||
} else {
|
||||
/*
|
||||
Otherwise we use the order.price.
|
||||
We use the order.price when the average is not available.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = order.price
|
||||
}
|
||||
@@ -131,6 +138,7 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
|
||||
let previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
recalculateActualSize()
|
||||
recalculateSizePlaced()
|
||||
|
||||
@@ -145,7 +153,10 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
got the Order Size in Base Asset, either from direct imput from the user or calculated
|
||||
from the Order Size in Quoted Asset input it by the user. So here we go.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.orderStatistics.actualRate.value
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value =
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.actualRate.value
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
}
|
||||
|
||||
@@ -173,7 +184,7 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
await applyFeePercentage(
|
||||
tradingEngineOrder.orderBaseAsset.feesToBePaid,
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
@@ -183,7 +194,7 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
}
|
||||
case 'Limit Order': {
|
||||
await applyFeePercentage(
|
||||
tradingEngineOrder.orderBaseAsset.feesToBePaid,
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
@@ -209,24 +220,27 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
|
||||
async function percentageFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
Percentage Filled Calculation: The only information we get from the exchange is the order.filled.
|
||||
We know this field will go up until reaching the Size To Be Filled. Once it reaches that number
|
||||
Percentage Filled Calculation: The only relevant information we get from the exchange is the order.filled.
|
||||
We know this field will go up until reaching the Actual Size. Once it reaches that number
|
||||
the order will be 100% filled.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (tradingEngineOrder.orderBaseAsset.sizeToBeFilled.value)
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / tradingEngineOrder.orderBaseAsset.actualSize.value
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
|
||||
}
|
||||
|
||||
async function feesPaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
The exchange fees are taken from the Base Asset or the Quoted Asset depending if we
|
||||
are buying or selling. Within the order information received from the exchange we can not see the fees paid so we need to
|
||||
calculate them ourselves. To do this it is needed to be used the Session Fees Parameters.
|
||||
Within the order information received from the exchange we can not see the fees paid so we need to
|
||||
calculate them ourselves. Knwowing the Fees Paid is critical to later update the balances correctly,
|
||||
since at every trade we will receive less than just the Actual Size. In fact we will receive the Actual
|
||||
Size minus the Fees Paid.
|
||||
|
||||
The percentage at which the exchange will charge fees needs to be configured at the the Session Fees Parameters.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
await applyFeePercentage(
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid,
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
@@ -236,7 +250,7 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
}
|
||||
case 'Limit Order': {
|
||||
await applyFeePercentage(
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid,
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
@@ -251,18 +265,16 @@ exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
/*
|
||||
CCXT returns order.filled with an amount denominated in Base Asset. We will
|
||||
take it from there for our Order Base Asset. The amount in order.filled does
|
||||
not account for the fees, which is exactly what we need in this case.
|
||||
not reflect the Fees Paid, even if we are paying the fees in Base Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10)
|
||||
/*
|
||||
For Quoted Asset, CCXT does not return any field with the size filled, so we
|
||||
need to calculate that by ourselves. Since we know the Size in Quoted Asset thet
|
||||
is already adjusted with the Actual Rate and the Percentage Filled, we will use all
|
||||
this in this formula to get the Size Filled in Quoted Asset.
|
||||
need to calculate that by ourselves.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
|
||||
tradingEngineOrder.orderQuotedAsset.sizeToBeFilled.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
|
||||
}
|
||||
|
||||
@@ -665,64 +665,58 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
/* Balances Update */
|
||||
switch (true) {
|
||||
/*
|
||||
Let's remember that for Buy orders, the fees are paid in Base Asset.So we
|
||||
need to substract from the balance of Quoted Asset both the size filled and
|
||||
the fees, and we will receive at the balance of Base Asset just the size filled,
|
||||
bacuase the exchange gets the fees in Base Asset for them.
|
||||
*/
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
|
||||
/* Balance Base Asset: Undo the previous accounting */
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value -
|
||||
previousBaseAssetSizeFilled
|
||||
|
||||
/* Balance Base Asset: Account the current filling and fees */
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value +
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value
|
||||
|
||||
/* Balance Quoted Asset: Undo the previous accounting */
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value +
|
||||
previousQuotedAssetSizeFilled +
|
||||
previousQuotedAssetFeesPaid
|
||||
|
||||
/* Balance Quoted Asset: Account the current filling and fees */
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value -
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
/*
|
||||
For Sell orders, the fees are being paid in Quote Asset, so we will substract
|
||||
from the Base Asset balance the size filled and the fees, and we will add
|
||||
to the Quoted Asset balance only the size filled, because the exchange
|
||||
get those fees.
|
||||
For Sell orders, the fees are being paid in Quote Asset.
|
||||
*/
|
||||
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
|
||||
|
||||
/* Balance Base Asset: Undo the previous accounting */
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value +
|
||||
previousBaseAssetSizeFilled
|
||||
|
||||
/* Balance Base Asset: Account the current filling and fees */
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value -
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value
|
||||
|
||||
/* Balance Quoted Asset: Undo the previous accounting */
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value -
|
||||
previousQuotedAssetSizeFilled +
|
||||
previousQuotedAssetFeesPaid
|
||||
|
||||
/* Balance Quoted Asset: Account the current filling and fees */
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value +
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
/*
|
||||
Let's remember that for Buy orders, the fees are paid in Base Asset.
|
||||
*/
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
|
||||
/* Balance Base Asset: Undo the previous accounting */
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value -
|
||||
previousBaseAssetSizeFilled +
|
||||
previousBaseAssetFeesPaid
|
||||
|
||||
/* Balance Base Asset: Account the current filling and fees */
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value -
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value +
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value
|
||||
|
||||
/* Balance Quoted Asset: Undo the previous accounting */
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value -
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value +
|
||||
previousQuotedAssetSizeFilled
|
||||
|
||||
/* Balance Quoted Asset: Account the current filling and fees */
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value -
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value
|
||||
break
|
||||
}
|
||||
@@ -848,12 +842,19 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
|
||||
}
|
||||
|
||||
async function applyFeePercentage(feesNode, tradingEngineOrder, tradingSystemOrder, feePercentage, percentageFilled) {
|
||||
async function applyFeePercentage(feesNodePropertyName, tradingEngineOrder, tradingSystemOrder, feePercentage, percentageFilled) {
|
||||
/*
|
||||
The exchange fees are taken from the Base Asset or the Quoted Asset depending if we
|
||||
are buying or selling.
|
||||
*/
|
||||
let feesNode
|
||||
|
||||
switch (true) {
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
/*
|
||||
In this case the fees are taken from the Base Asset you receive as a result of the trade at the exchange.
|
||||
*/
|
||||
feesNode = tradingEngineOrder.orderBaseAsset[feesNodePropertyName]
|
||||
feesNode.value =
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value *
|
||||
feePercentage / 100 *
|
||||
@@ -864,6 +865,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
/*
|
||||
In this case the fees are taken from the Quoted Asset you receive as a result of the trade at the exchange.
|
||||
*/
|
||||
feesNode = tradingEngineOrder.orderQuotedAsset[feesNodePropertyName]
|
||||
feesNode.value =
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value *
|
||||
feePercentage / 100 *
|
||||
@@ -872,8 +874,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
}
|
||||
}
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10)
|
||||
feesNode.value = global.PRECISE(feesNode.value, 10)
|
||||
feesNode.value = global.PRECISE(feesNode.value, 10)
|
||||
}
|
||||
|
||||
async function checkOrderEvent(event, order, executionAlgorithm, executionNode) {
|
||||
|
||||
@@ -888,14 +888,18 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
|
||||
function checkIfStageNeedsToBeClosed(tradingEngineStage, tradingSystemStage, stageName) {
|
||||
/*
|
||||
The Stage is closed when the placedSize reaches the targetSize, and the fillSize
|
||||
+ feesPaid reaches the placedSize. This depends on how the user defined the stage:
|
||||
either on Base Asset or Quoted Asset.
|
||||
|
||||
This can happens at any time when we update the sizeFilled and the feesPaid values
|
||||
when we see at the exchange that orders were filled.
|
||||
|
||||
Note that the comparison is made with a Rounding Factor in order to avoid rounding problems.
|
||||
The Stage is closed when the fillSize + feesPaid reaches the targetSize.
|
||||
|
||||
The Target Size is defined by the end user either in Base Asset or Quoted Asset.
|
||||
Whatever the user chooses defines also the way we check if the Target was met.
|
||||
Why? Because the counterparty target is estimated with the Target Rate and it
|
||||
is not syncronized with the reality we later learn when the exchange tell us the
|
||||
actual rate each of the orders where executed at.
|
||||
|
||||
For the same reason that the user does not know exactly which is the rate the orders
|
||||
will actually be filled, then we can not expect that the total filled will really
|
||||
reach the target size, so we introduce a rounding factor so that when it is close
|
||||
enough we will consider the target to have been reached.
|
||||
*/
|
||||
const ROUNDING_ERROR_CORRECTION_FACTOR = 1.001
|
||||
|
||||
@@ -903,8 +907,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
case 'Base Asset': {
|
||||
if (
|
||||
(
|
||||
tradingEngineStage.stageBaseAsset.sizeFilled.value +
|
||||
tradingEngineStage.stageBaseAsset.feesPaid.value
|
||||
tradingEngineStage.stageBaseAsset.sizeFilled.value
|
||||
)
|
||||
* ROUNDING_ERROR_CORRECTION_FACTOR
|
||||
>=
|
||||
@@ -919,8 +922,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
case 'Quoted Asset': {
|
||||
if (
|
||||
(
|
||||
tradingEngineStage.stageQuotedAsset.sizeFilled.value +
|
||||
tradingEngineStage.stageQuotedAsset.feesPaid.value
|
||||
tradingEngineStage.stageQuotedAsset.sizeFilled.value
|
||||
)
|
||||
* ROUNDING_ERROR_CORRECTION_FACTOR
|
||||
>=
|
||||
|
||||
Reference in new issue
Block a user