Skip to content

Commit 73ed332

Browse files
author
Fernando Fernandes
committed
Add FTX place stop limit order support
- Refactor around the trailing space logic, making it more reusable and isolated.
1 parent 0a0313b commit 73ed332

21 files changed

+387
-73
lines changed

Sources/SwiftTrader/Model/Error/SwiftTraderError+Details.swift

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,31 @@ import Foundation
1111
public extension SwiftTraderError {
1212

1313
static func error(for operation: SwiftTraderOperation, statusCode: Int, localizedErrorMessage: String, data: Data) -> SwiftTraderError {
14-
1514
switch operation {
16-
17-
case .ftxPositions:
18-
return .ftxStatusCodeNotOK(statusCode: statusCode, localizedErrorMessage: localizedErrorMessage)
19-
15+
case .ftxPositions, .ftxPlaceStopLimitOrder:
16+
guard let ftxError = try? JSONDecoder().decode(FTXError.self, from: data) else {
17+
return .ftxStatusCodeNotOK(statusCode: statusCode, localizedErrorMessage: localizedErrorMessage)
18+
}
19+
return .ftxStatusCodeNotOK(
20+
statusCode: statusCode,
21+
localizedErrorMessage: localizedErrorMessage,
22+
isSuccess: ftxError.isSuccess,
23+
errorMessage: ftxError.errorMessage
24+
)
2025
case .kucoinFuturesAccountOverview,
2126
.kucoinFuturesCancelStopOrders,
2227
.kucoinFuturesOrderList,
2328
.kucoinFuturesStopOrderList,
2429
.kucoinFuturesPlaceStopLimitOrder,
2530
.kucoinFuturesPositionList:
26-
2731
guard let kucoinError = try? JSONDecoder().decode(KucoinSystemError.self, from: data) else {
2832
return .kucoinStatusCodeNotOK(statusCode: statusCode, localizedErrorMessage: localizedErrorMessage)
2933
}
30-
3134
return .kucoinStatusCodeNotOK(
3235
statusCode: statusCode,
3336
localizedErrorMessage: localizedErrorMessage,
34-
kucoinErrorCode: kucoinError.code,
35-
kucoinErrorMessage: kucoinError.message
37+
errorCode: kucoinError.code,
38+
errorMessage: kucoinError.message
3639
)
3740
}
3841
}

Sources/SwiftTrader/Model/Error/SwiftTraderError.swift

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,41 @@ public enum SwiftTraderError: Error {
1818
/// The returned model is unexpected.
1919
case unexpectedResponse(modelString: String)
2020

21+
// MARK: Target Price Calculation
22+
23+
/// Something went wrong while trying to set the target price.
24+
case couldNotCalculateTargetPrice(input: SwiftTraderStopLimitOrderInput)
25+
26+
/// The offsett has to be lower than the percentage of the profit; the order will not be placed.
27+
case invalidOffset(offset: Double, profitPercentage: Double)
28+
29+
/// (`long` position) The target price is lower than the entry price; the order will not be placed.
30+
case invalidTargetPriceTooLow(entryPrice: String, targetPrice: String)
31+
32+
/// (`short` position) The target price is higher than the entry price; the order will not be placed.
33+
case invalidTargetPriceTooHigh(entryPrice: String, targetPrice: String)
34+
2135
// MARK: - FTX Related
2236

37+
/// The number of the assets that were bought has to be greater than zero.
38+
case ftxInvalidSize
39+
2340
/// No `FTXAuth` instance was given; it will be impossible to authenticate with FTX.
2441
case ftxMissingAuthentication
2542

2643
/// And error ocurred while executing the function `SwiftTrader.ftxPositions`.
2744
case ftxPositions(error: Error)
2845

46+
/// And error ocurred while executing the function `SwiftTrader.ftxPlaceStopLimitOrder`.
47+
case ftxPlaceStopLimitOrder(error: Error)
48+
2949
/// The response status code is something other than `200`.
30-
case ftxStatusCodeNotOK(statusCode: Int, localizedErrorMessage: String)
50+
case ftxStatusCodeNotOK(
51+
statusCode: Int,
52+
localizedErrorMessage: String,
53+
isSuccess: Bool? = nil,
54+
errorMessage: String? = nil
55+
)
3156

3257
// MARK: - Kucoin Related
3358

@@ -40,8 +65,8 @@ public enum SwiftTraderError: Error {
4065
case kucoinStatusCodeNotOK(
4166
statusCode: Int,
4267
localizedErrorMessage: String,
43-
kucoinErrorCode: String? = nil,
44-
kucoinErrorMessage: String? = nil
68+
errorCode: String? = nil,
69+
errorMessage: String? = nil
4570
)
4671

4772
// MARK: Account Overview
@@ -56,18 +81,6 @@ public enum SwiftTraderError: Error {
5681

5782
// MARK: Orders
5883

59-
/// Something went wrong while trying to set the target price.
60-
case kucoinCouldNotCalculateTheTargetPrice(input: SwiftTraderStopLimitOrderInput)
61-
62-
/// The offsett has to be lower than the percentage of the profit; the order will not be placed.
63-
case kucoinInvalidOffset(offset: Double, profitPercentage: Double)
64-
65-
/// (`long` position) The target price is lower than the entry price; the order will not be placed.
66-
case kucoinInvalidTargetPriceLower(entryPrice: String, targetPrice: String)
67-
68-
/// (`short` position) The target price is higher than the entry price; the order will not be placed.
69-
case kucoinInvalidTargetPriceHigher(entryPrice: String, targetPrice: String)
70-
7184
/// And error ocurred while executing the function `SwiftTrader.kucoinFuturesOrderList(orderStatus:)`.
7285
case kucoinFuturesOrderList(error: Error)
7386

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// FTXError.swift
3+
//
4+
//
5+
// Created by Fernando Fernandes on 08.03.22.
6+
//
7+
8+
import Foundation
9+
10+
/// Encapsulates FTX errors that may occur while interacting with its REST APIs.
11+
public struct FTXError: Codable {
12+
public let isSuccess: Bool
13+
public let errorMessage: String
14+
15+
enum CodingKeys: String, CodingKey {
16+
case isSuccess = "success"
17+
case errorMessage = "error"
18+
}
19+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// FTXOrderParameters.swift
3+
//
4+
//
5+
// Created by Fernando Fernandes on 08.03.22.
6+
//
7+
8+
import Foundation
9+
10+
/// Encapsulates parameters do place an order, for example via `FTXPlaceOrderRequest`.
11+
public struct FTXTriggerOrderParameters {
12+
13+
// MARK: - Properties
14+
15+
public let market: String
16+
public let side: OrderSide
17+
public let size: Double
18+
public let type: FTXTTriggerOrderType
19+
public let reduceOnly: Bool
20+
public let retryUntilFilled: Bool
21+
public let triggerPrice: Double
22+
public let orderPrice: Double
23+
24+
// MARK: - Lifecycle
25+
26+
public init(market: String,
27+
side: OrderSide,
28+
size: Double,
29+
type: FTXTTriggerOrderType,
30+
reduceOnly: Bool,
31+
retryUntilFilled: Bool,
32+
triggerPrice: Double,
33+
orderPrice: Double) {
34+
self.market = market
35+
self.side = side
36+
self.size = size
37+
self.type = type
38+
self.reduceOnly = reduceOnly
39+
self.retryUntilFilled = retryUntilFilled
40+
self.triggerPrice = triggerPrice
41+
self.orderPrice = orderPrice
42+
}
43+
}
44+
45+
/// Holds the keys of the parameters for placing a FTX trigger order.
46+
public enum FTXTriggerOrderParameterKey: String {
47+
case market
48+
case side
49+
case size
50+
case type
51+
case reduceOnly
52+
case retryUntilFilled
53+
case triggerPrice
54+
case orderPrice
55+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// FTXOrderType.swift
3+
//
4+
//
5+
// Created by Fernando Fernandes on 08.03.22.
6+
//
7+
8+
import Foundation
9+
10+
public enum FTXTTriggerOrderType: String, Codable {
11+
case stop
12+
case trailingStop
13+
case takeProfit
14+
}

Sources/SwiftTrader/Model/FTX/Responses/FTXPlaceTriggerOrder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public struct FTXOrder: Codable {
3737
public let size: Double
3838
public let status: String
3939
public let type: String
40-
public let orderPrice: String?
40+
public let orderPrice: Double?
4141
public let error: String?
4242
public let triggeredAt: String?
4343
public let reduceOnly: Bool
@@ -75,7 +75,7 @@ public struct FTXOrder: Codable {
7575
self.size = try container.decode(Double.self, forKey: .size)
7676
self.status = try container.decode(String.self, forKey: .status)
7777
self.type = try container.decode(String.self, forKey: .type)
78-
self.orderPrice = try container.decodeIfPresent(String.self, forKey: .orderPrice)
78+
self.orderPrice = try container.decodeIfPresent(Double.self, forKey: .orderPrice)
7979
self.error = try container.decodeIfPresent(String.self, forKey: .error)
8080
self.triggeredAt = try container.decodeIfPresent(String.self, forKey: .triggeredAt)
8181
self.reduceOnly = try container.decode(Bool.self, forKey: .reduceOnly)

Sources/SwiftTrader/Model/FTX/Responses/FTXPositionList.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ public struct FTXPosition: Codable {
2323
public let cost: Double
2424
public let cumulativeBuySize: Double?
2525
public let cumulativeSellSize: Double?
26-
public let entryPrice: Double
27-
public let estimatedLiquidationPrice: Double
26+
public let entryPrice: Double?
27+
public let estimatedLiquidationPrice: Double?
2828
public let future: String
2929
public let initialMarginRequirement: Double
3030
public let longOrderSize: Double
@@ -71,8 +71,8 @@ public struct FTXPosition: Codable {
7171
self.cost = try container.decode(Double.self, forKey: .cost)
7272
self.cumulativeBuySize = try container.decodeIfPresent(Double.self, forKey: .cumulativeBuySize)
7373
self.cumulativeSellSize = try container.decodeIfPresent(Double.self, forKey: .cumulativeSellSize)
74-
self.entryPrice = try container.decode(Double.self, forKey: .entryPrice)
75-
self.estimatedLiquidationPrice = try container.decode(Double.self, forKey: .estimatedLiquidationPrice)
74+
self.entryPrice = try container.decodeIfPresent(Double.self, forKey: .entryPrice)
75+
self.estimatedLiquidationPrice = try container.decodeIfPresent(Double.self, forKey: .estimatedLiquidationPrice)
7676
self.future = try container.decode(String.self, forKey: .future)
7777
self.initialMarginRequirement = try container.decode(Double.self, forKey: .initialMarginRequirement)
7878
self.longOrderSize = try container.decode(Double.self, forKey: .longOrderSize)
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//
2-
// KucoinOrderSide.swift
2+
// OrderSide.swift
33
//
44
//
55
// Created by Fernando Fernandes on 05.02.22.
66
//
77

88
import Foundation
99

10-
public enum KucoinOrderSide: String, Codable {
10+
public enum OrderSide: String, Codable {
1111
case buy
1212
case sell
1313
}

Sources/SwiftTrader/Model/Kucoin/KucoinError.swift renamed to Sources/SwiftTrader/Model/Kucoin/KucoinSystemError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// KucoinError.swift
2+
// KucoinSystemError.swift
33
//
44
//
55
// Created by Fernando Fernandes on 28.01.22.

Sources/SwiftTrader/Model/Kucoin/Order/KucoinFuturesOrderList.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public struct KucoinFuturesOrder: Codable {
3434
public let symbol: String
3535

3636
public let type: KucoinOrderType
37-
public let side: KucoinOrderSide
37+
public let side: OrderSide
3838

3939
/// The price of one asset's unit. Although this is returned in Sandbox,
4040
/// it returns `null` in production. Therefore it's an optional.
@@ -112,7 +112,7 @@ public struct KucoinFuturesOrder: Codable {
112112
self.id = try container.decode(String.self, forKey: .id)
113113
self.symbol = try container.decode(String.self, forKey: .symbol)
114114
self.type = try container.decode(KucoinOrderType.self, forKey: .type)
115-
self.side = try container.decode(KucoinOrderSide.self, forKey: .side)
115+
self.side = try container.decode(OrderSide.self, forKey: .side)
116116
self.price = try container.decodeIfPresent(String.self, forKey: .price)
117117
self.size = try container.decode(Int.self, forKey: .size)
118118
self.value = try container.decode(String.self, forKey: .value)

Sources/SwiftTrader/Model/Kucoin/Order/KucoinOrderParameters.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public struct KucoinOrderParameters {
1616

1717
public let clientOid = UUID().uuidString
1818
public let symbol: String
19-
public let side: KucoinOrderSide
19+
public let side: OrderSide
2020
public let type: KucoinOrderType
2121
public let stop: KucoinOrderStop
2222
public let stopPriceType: KucoinOrderStopPriceType
@@ -28,7 +28,7 @@ public struct KucoinOrderParameters {
2828
// MARK: - Lifecycle
2929

3030
public init(symbol: String,
31-
side: KucoinOrderSide,
31+
side: OrderSide,
3232
type: KucoinOrderType,
3333
stop: KucoinOrderStop,
3434
stopPriceType: KucoinOrderStopPriceType,

Sources/SwiftTrader/Model/SwiftTraderExchange.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Foundation
99

1010
/// The list of supported exchanges.
1111
public enum SwiftTraderExchange: String, Codable {
12-
case Binance
13-
case FTX
14-
case Kucoin
12+
case binance
13+
case ftx
14+
case kucoin
1515
}

Sources/SwiftTrader/Model/SwiftTraderOperation.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ import Foundation
99

1010
/// The currently running `SwiftTrader` operation.
1111
public enum SwiftTraderOperation {
12+
13+
// MARK: - FTX
14+
1215
case ftxPositions
16+
case ftxPlaceStopLimitOrder
17+
18+
// MARK: - Kucoin
19+
1320
case kucoinFuturesAccountOverview
1421
case kucoinFuturesCancelStopOrders
1522
case kucoinFuturesOrderList

Sources/SwiftTrader/Model/SwiftTraderOrderInput.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public struct SwiftTraderStopLimitOrderInput {
2020
public let isLong: Bool
2121
public let offset: Double
2222
public let profitPercentage: Double
23+
public let size: Double
2324
public let ticker: String
2425
public let tickerSize: String
2526

@@ -42,6 +43,7 @@ public struct SwiftTraderStopLimitOrderInput {
4243
/// of the current price (`1.0%` - `0.75%`). Using the same `offset`, if the `profitPercentage` is now `2.0%`, the stop order
4344
/// will be placed at `1.25%` of the current price (`2.0%` - `0.75%`).
4445
/// - profitPercentage: The percentage of the profit at this point, e.g.: "1.5", "0.67".
46+
/// - size: How much assets were bought.
4547
/// - ticker: E.g.: BTCUSDT
4648
/// - tickerSize: E.g.: "1", "0.05", "0.00001"
4749
public init(cancelStopOrders: Bool,
@@ -52,6 +54,7 @@ public struct SwiftTraderStopLimitOrderInput {
5254
isLong: Bool,
5355
offset: Double,
5456
profitPercentage: Double,
57+
size: Double,
5558
ticker: String,
5659
tickerSize: String) {
5760
self.cancelStopOrders = cancelStopOrders
@@ -62,6 +65,7 @@ public struct SwiftTraderStopLimitOrderInput {
6265
self.isLong = isLong
6366
self.offset = offset
6467
self.profitPercentage = profitPercentage
68+
self.size = size
6569
self.ticker = ticker
6670
self.tickerSize = tickerSize
6771
}

Sources/SwiftTrader/Network/FTX/FTXAPI.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public struct FTXAPI {
2121
}
2222

2323
public struct Path {
24-
static let positions = "/api/positions"
24+
static let positions = "/api/positions"
25+
static let conditionalOrders = "/api/conditional_orders"
2526
}
2627
}

0 commit comments

Comments
 (0)