@@ -14,7 +14,7 @@ import Foundation
1414/// such instance is based on a given `SwiftTraderOrderInput`/`offset`.
1515public extension SwiftTrader {
1616
17- func createOrderParameters( for input: SwiftTraderOrderInput ) -> KucoinOrderParameters {
17+ func createOrderParameters( for input: SwiftTraderOrderInput ) throws -> KucoinOrderParameters {
1818 logger. log ( " Creating order parameters... " )
1919
2020 // E.g: 7.47 -> 0.0747
@@ -31,50 +31,73 @@ public extension SwiftTrader {
3131
3232 // E.g.: (0.0747 - 0.0073) = 0.0674 (6,74%)
3333 // Use "abs" to filter out negative numbers.
34- let targetPercentage : Double = abs ( profitPercentage - offset)
34+ let targetPercentage : Double = profitPercentage - offset
3535 logger. log ( " Target percentage: \( targetPercentage. toDecimalString ( ) ) " )
3636
3737 // E.g.: 42000.69 * 0.0674 = 2830.846506
3838 let priceIncrement : Double = input. entryPrice * targetPercentage
3939 logger. log ( " Price increment: \( priceIncrement. toDecimalString ( ) ) " )
4040
41- // E.g.: 42000.9 + 2830.846506 = 44831.536506
41+ // E.g.: 42000.69 + 2830.846506 = 44831.536506
4242 let targetPrice : Double = input. entryPrice + priceIncrement
4343 logger. log ( " Entry price: \( input. entryPrice. toDecimalString ( ) ) " )
4444 logger. log ( " Target price: \( targetPrice. toDecimalString ( ) ) " )
4545
46- // Now in order to avoid a huge difference between the entry price and the target price in terms of size,
47- // "normalize" them based on the entry price by first counting its characters.
48- let entryPriceCount = input. entryPrice. toDecimalString ( ) . count
49- logger. log ( " Entry price count: \( entryPriceCount) " )
46+ var targetPriceString = " \( targetPrice. toDecimalString ( ) ) "
5047
51- // Count the target price characters too.
52- let targetPriceCount = targetPrice. toDecimalString ( ) . count
53- logger. log ( " Target price count: \( targetPriceCount) " )
54-
55- // E.g.: 12 - 8 (44831.536506 - 42000.69)
56- // "44831.536506" becomes "44831.53".
57- let charactersToBeRemoved = abs ( targetPriceCount - entryPriceCount)
58- var targetPriceString = " \( targetPrice. toDecimalString ( ) ) " . dropLast ( charactersToBeRemoved)
59- logger. log ( " Target price normalized: \( targetPriceString) " )
48+ // Finally, handle the following requirement:
49+ //
50+ // "The price specified must be a multiple number of the contract tickSize, otherwise the system will report an
51+ // error when you place the order. The tick size is the smallest price increment in which the prices are quoted.
52+ guard let tickerSizeDouble = Double ( input. tickerSize) ,
53+ let priceDouble = Double ( targetPriceString) else {
54+ throw SwiftTraderError . kucoinCouldNotCalculateTheTargetPrice ( input: input)
55+ }
6056
61- // Finally, avoid the following Kucoin error with minimal effort: "The parameter shall be a multiple of ..."
62- // First, just try replacing the last character by "1". E.g.: "0.00002347" becomes "0.00002341"
63- if targetPriceString. components ( separatedBy: " . " ) . count > 1 {
64- targetPriceString = targetPriceString. dropLast ( ) + " 1 "
57+ // Whole ticker size like "1", "2", etc.
58+ if tickerSizeDouble. truncatingRemainder ( dividingBy: 1 ) == 0 {
59+ // E.g.: "44831.53" becomes "44832".
60+ targetPriceString = " \( priceDouble. rounded ( . up) . toDecimalString ( ) ) "
61+
6562 } else {
66- // Handles whole numbers: "3735" becomes "3735.1" (instead of "3731").
67- targetPriceString += " .1 "
63+ // Handle ticker sizes like "0.0001", "0.05", "0.00000001", etc.
64+ //
65+ // E.g., considering "0.0001":
66+ // - "0.023" becomes "0.0231"
67+ // - "0.0456" becomes "0.0451"
68+ // - "0.7" becomes "0.7001"
69+ guard let targetPriceLastDigit = targetPriceString. last,
70+ let tickerLastDigit = input. tickerSize. last else {
71+ throw SwiftTraderError . kucoinCouldNotCalculateTheTargetPrice ( input: input)
72+ }
73+
74+ let tickerDigits = input. tickerSize. decimalCount ( )
75+ let targetPriceDigits = targetPriceString. decimalCount ( )
76+
77+ if targetPriceDigits == tickerDigits, targetPriceLastDigit != tickerLastDigit {
78+ targetPriceString = targetPriceString. dropLast ( ) + " \( tickerLastDigit) "
79+
80+ } else if targetPriceDigits > tickerDigits {
81+ let digitsToRemove = ( targetPriceDigits - tickerDigits) + 1
82+ targetPriceString = targetPriceString. dropLast ( digitsToRemove) + " \( tickerLastDigit) "
83+
84+ } else if targetPriceDigits < tickerDigits {
85+ let digitsToAdd = ( tickerDigits - targetPriceDigits)
86+ let digits : [ String ] = Array ( repeating: " 0 " , count: digitsToAdd)
87+ targetPriceString = targetPriceString + digits. joined ( separator: " " )
88+ targetPriceString = targetPriceString. dropLast ( ) + " \( tickerLastDigit) "
89+ }
6890 }
91+
92+ logger. log ( " Ticker size: \( input. tickerSize) " )
6993 logger. log ( " Target price string: \( targetPriceString) " )
7094
71- // E.g.: "44831.53" becomes "44831".
72- // Workaround to not call "https://docs.kucoin.com/futures/#get-open-contract-list" (for now).
73- // "The price specified must be a multiple number of the contract tickSize, otherwise the system will report an
74- // error when you place the order. The tick size is the smallest price increment in which the prices are quoted.
75- if let priceDouble = Double ( targetPriceString) , priceDouble > 10 {
76- targetPriceString = " \( priceDouble. rounded ( . down) . toDecimalString ( ) ) "
77- logger. log ( " Target price string: \( targetPriceString) " )
95+ // Throw in case the target price became lower than the entry price for whatever reason.
96+ // Do not place an order in this scenario.
97+ guard Double ( targetPriceString) ?? 0 > input. entryPrice else {
98+ throw SwiftTraderError . kucoinInvalidTargetPrice (
99+ entryPrice: input. entryPrice. toDecimalString ( ) ,
100+ targetPrice: targetPriceString)
78101 }
79102
80103 return KucoinOrderParameters (
0 commit comments