Skip to content

Commit 181c728

Browse files
author
Fernando Fernandes
committed
More offset fixes
1 parent 6f0d427 commit 181c728

File tree

5 files changed

+85
-47
lines changed

5 files changed

+85
-47
lines changed

Sources/SwiftTrader/Extensions/Double+Decimal.swift

-17
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,4 @@ public extension Double {
1313
func toDecimalString() -> String {
1414
"\(NSNumber(value: Double(self)).decimalValue)"
1515
}
16-
17-
/// Returns the decimal numbers of `self`.
18-
///
19-
/// Examples:
20-
/// - 43865.52: returns "2"
21-
/// - 0.934: returns "3"
22-
/// - 0.00003456: returns "8"
23-
/// - 2: returns "0"
24-
func decimalCount() -> Int {
25-
if self == Double(Int(self)) {
26-
return 0
27-
}
28-
let integerString = "\(NSNumber(value: Int(self)).decimalValue)"
29-
let doubleString = Double(self).toDecimalString()
30-
let decimalCount = doubleString.count - integerString.count - 1
31-
return decimalCount
32-
}
3316
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// String+Decimal.swift
3+
//
4+
//
5+
// Created by Fernando Fernandes on 12.02.22.
6+
//
7+
8+
import Foundation
9+
10+
public extension String {
11+
12+
/// Returns the decimal numbers of `self`.
13+
///
14+
/// Examples:
15+
/// - 43865.52: returns "2"
16+
/// - 0.934: returns "3"
17+
/// - 0.00003456: returns "8"
18+
/// - 2: returns "0"
19+
func decimalCount() -> Int {
20+
let decimalPartString = self.split(separator: ".").last
21+
return decimalPartString?.count ?? 0
22+
}
23+
}

Sources/SwiftTrader/Model/Error/SwiftTraderError.swift

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public enum SwiftTraderError: Error {
2020
/// And error ocurred while executing the function `SwiftTrader.kucoinFuturesAccountOverview(currencySymbol:)`.
2121
case kucoinFuturesAccountOverviewError(error: Error)
2222

23+
/// Something went wrong while trying to set the target price.
24+
case kucoinCouldNotCalculateTheTargetPrice(input: SwiftTraderOrderInput)
25+
26+
/// The target price is lower than the entry price; the order will not be placed.
27+
case kucoinInvalidTargetPrice(entryPrice: String, targetPrice: String)
28+
2329
/// And error ocurred while executing the function `SwiftTrader.kucoinFuturesPlaceOrder()`.
2430
case kucoinPlaceOrderError(error: Error)
2531

Sources/SwiftTrader/SwiftTrader+Offset.swift

+52-29
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Foundation
1414
/// such instance is based on a given `SwiftTraderOrderInput`/`offset`.
1515
public 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(

Sources/SwiftTrader/SwiftTrader.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,11 @@ public extension SwiftTrader {
7676
// MARK: Place Orders
7777

7878
func kucoinFuturesPlaceOrder(_ orderInput: SwiftTraderOrderInput) async throws -> Result<KucoinFuturesPlaceOrder, SwiftTraderError> {
79+
80+
let orderParameters = try createOrderParameters(for: orderInput)
81+
7982
let request = KucoinFuturesPlaceOrdersRequest(
80-
orderParameters: createOrderParameters(for: orderInput),
83+
orderParameters: orderParameters,
8184
kucoinAuth: kucoinAuth,
8285
settings: settings.networkRequestSettings
8386
)

0 commit comments

Comments
 (0)