Skip to content

Commit f42fd35

Browse files
committed
Finish placing MARKET orders within Binance
- Update order response. - Update signee. - Make FTX auth optional with nil as default value.
1 parent cbbdbee commit f42fd35

File tree

6 files changed

+77
-43
lines changed

6 files changed

+77
-43
lines changed

Sources/SwiftTrader/Model/Binance/Order/BinanceSpotNewOrderParameters.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,6 @@ public enum BinanceSpotNewOrderParameterKey: String {
3838
case side
3939
case type
4040
case quoteOrderQty
41+
case timestamp
42+
case signature
4143
}

Sources/SwiftTrader/Model/Binance/Order/BinanceSpotNewOrderResponse.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@ public struct BinanceSpotNewOrderResponse: Codable {
1717
let transactTime: Int
1818
let price, origQty, executedQty, cummulativeQuoteQty: String
1919
let status, timeInForce, type, side: String
20-
let strategyID, strategyType: Int
20+
let workingTime: Int
2121
let fills: [BinanceOrderFill]
22+
let selfTradePreventionMode: String
2223

2324
enum CodingKeys: String, CodingKey {
2425
case symbol
2526
case orderID = "orderId"
2627
case orderListID = "orderListId"
2728
case clientOrderID = "clientOrderId"
28-
case transactTime, price, origQty, executedQty, cummulativeQuoteQty, status, timeInForce, type, side
29-
case strategyID = "strategyId"
30-
case strategyType, fills
29+
case transactTime, price, origQty, executedQty, cummulativeQuoteQty, status, timeInForce, type, side, workingTime, fills, selfTradePreventionMode
3130
}
3231
}
3332

Sources/SwiftTrader/Network/Binance/Orders/PlaceOrder/BinanceSpotNewOrderRequest.swift

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,21 @@ public struct BinanceSpotNewOrderRequest: NetworkRequest {
3232
var urlRequest = URLRequest(url: try spotNewOrderResource.url)
3333
urlRequest.httpMethod = HTTPMethod.POST.rawValue
3434

35-
// Parameters.
36-
let dataParameter = try createDataParameter(from: orderParameters)
37-
urlRequest.httpBody = dataParameter
35+
// Body parameters.
36+
let stringParameters = createStringParameters(from: orderParameters)
37+
let dataParameters = try createDataParameter(from: stringParameters)
38+
39+
// Signature parameter.
40+
let stringSignature = try createSignature(for: dataParameters, binanceAuth: binanceAuth)
41+
let dataSignature = try createDataParameter(from: stringParameters + "&\(stringSignature)")
42+
43+
// Add the parameters.
44+
urlRequest.httpBody = dataSignature
45+
46+
// Add header fields.
3847
urlRequest.addValue(HTTPHeader.Value.urlEncoded, forHTTPHeaderField: HTTPHeader.Field.contentType)
3948
try BinanceAPI.setRequestHeaderFields(request: &urlRequest, binanceAuth: binanceAuth.spot)
4049

41-
#warning("TODO: Add signature - here?")
42-
4350
return urlRequest
4451
}
4552
}
@@ -85,21 +92,34 @@ public extension BinanceSpotNewOrderRequest {
8592

8693
private extension BinanceSpotNewOrderRequest {
8794

88-
func createDataParameter(from orderParameters: BinanceSpotNewOrderParameters) throws -> Data {
89-
let symbolParameter = "\(BinanceSpotNewOrderParameterKey.symbol.rawValue)=\(orderParameters.symbol)"
90-
let sideParameter = "\(BinanceSpotNewOrderParameterKey.side.rawValue)=\(orderParameters.side)"
91-
let typeParameter = "\(BinanceSpotNewOrderParameterKey.type.rawValue)=\(orderParameters.type)"
92-
let quoteQtyParameter = "\(BinanceSpotNewOrderParameterKey.quoteOrderQty.rawValue)=\(orderParameters.quoteOrderQty)"
93-
94-
#warning("TODO: Add timestamp")
95-
96-
// E.g.: "symbol=BTCUSDT&side=buy&type=market&quoteOrderQty=100.0"
97-
let stringParameter = "\(symbolParameter)&\(sideParameter)&\(typeParameter)&\(quoteQtyParameter)"
98-
99-
if let dataParameter = stringParameter.data(using: .utf8) {
100-
return dataParameter
95+
/// Creates the `String` parameters to be later added into the request body.
96+
///
97+
/// For example: *symbol=BTCUSDT&side=buy&type=market&quoteOrderQty=100.0&timestamp=1670998246731*
98+
///
99+
func createStringParameters(from orderParameters: BinanceSpotNewOrderParameters) -> String {
100+
let symbolParam = "\(BinanceSpotNewOrderParameterKey.symbol.rawValue)=\(orderParameters.symbol)"
101+
let sideParam = "\(BinanceSpotNewOrderParameterKey.side.rawValue)=\(orderParameters.side)"
102+
let typeParam = "\(BinanceSpotNewOrderParameterKey.type.rawValue)=\(orderParameters.type)"
103+
let quoteQtyParam = "\(BinanceSpotNewOrderParameterKey.quoteOrderQty.rawValue)=\(orderParameters.quoteOrderQty)"
104+
let timestampParam = "\(BinanceSpotNewOrderParameterKey.timestamp.rawValue)=\(Date().timestampMilliseconds)"
105+
106+
return "\(symbolParam)&\(sideParam)&\(typeParam)&\(quoteQtyParam)&\(timestampParam)"
107+
}
108+
109+
/// Creates the `Data` parameter to be added into the request body via `urlRequest.httpBody = dataParameter`.
110+
func createDataParameter(from string: String) throws -> Data {
111+
if let data = string.data(using: .utf8) {
112+
return data
101113
} else {
102-
throw NetworkRequestError.invalidDataFromString(string: stringParameter)
114+
throw NetworkRequestError.invalidDataFromString(string: string)
103115
}
104116
}
117+
118+
func createSignature(for data: Data, binanceAuth: BinanceAuth) throws -> String {
119+
let signature = try NetworkRequestSignee.createHMACSignature(for: data,
120+
secret: binanceAuth.spot.apiSecret,
121+
isHexString: true)
122+
let symbolParam = "\(BinanceSpotNewOrderParameterKey.signature.rawValue)=\(signature)"
123+
return symbolParam
124+
}
105125
}

Sources/SwiftTrader/Network/Util/Signee.swift

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ public struct NetworkRequestSignee {
4444
)
4545
request.setValue(signature, forHTTPHeaderField: httpHeaderField)
4646
}
47+
48+
/// Creates a `SHA256 HMAC` signature using the given `secret`.
49+
///
50+
/// - Parameters:
51+
/// - data: The `Data` to be signed. This for example could be `Data` from a `String`
52+
/// using `stringToSign.data(using: .utf8)`. The `String` contains the parameters
53+
/// that require signing. Taking Binance as an example, that would be something like
54+
/// "symbol=BTCUSDT&side=buy&type=market&quoteOrderQty=100.0&timestamp=1670998246731".
55+
/// - secret: The API secret required to sign the request.
56+
/// - isHexString: if `true`, then the generated String signature will be encoded using `Data.hexStringEncoded()`.
57+
/// If `false` (the default), then `Data.base64EncodedString()` will be used instead.
58+
static func createHMACSignature(for data: Data,
59+
secret: String,
60+
isHexString: Bool = false) throws -> String {
61+
let key = try createSymmetricKey(from: secret)
62+
let signature = HMAC<SHA256>.authenticationCode(for: data, using: key)
63+
return isHexString ? Data(signature).hexStringEncoded() : Data(signature).base64EncodedString()
64+
}
4765
}
4866

4967
// MARK: - Private
@@ -88,21 +106,22 @@ private extension NetworkRequestSignee {
88106
}
89107

90108
static func createHMACSignature(for method: HTTPMethod,
91-
path: String,
92-
body: String,
93-
secret: String,
94-
isHexString: Bool = false) throws -> String {
95-
guard let secretData = secret.data(using: .utf8) else {
96-
throw NetworkRequestError.stringToDataFailed(string: secret)
97-
}
98-
let key = SymmetricKey(data: secretData)
109+
path: String,
110+
body: String,
111+
secret: String,
112+
isHexString: Bool = false) throws -> String {
99113
let timestamp = Date().timestampMilliseconds
100114
let stringToSign = "\(timestamp)" + method.rawValue + path + body
101115
guard let stringToSignData = stringToSign.data(using: .utf8) else {
102116
throw NetworkRequestError.stringToDataFailed(string: stringToSign)
103117
}
104-
let signature = HMAC<SHA256>.authenticationCode(for: stringToSignData, using: key)
105-
return isHexString ? Data(signature).hexStringEncoded() : Data(signature).base64EncodedString()
118+
return try createHMACSignature(for: stringToSignData, secret: secret, isHexString: isHexString)
119+
}
120+
121+
static func createSymmetricKey(from secret: String) throws -> SymmetricKey {
122+
guard let secretData = secret.data(using: .utf8) else {
123+
throw NetworkRequestError.stringToDataFailed(string: secret)
124+
}
125+
return SymmetricKey(data: secretData)
106126
}
107127
}
108-

Sources/SwiftTrader/SwiftTrader+BinanceSpot.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public extension SwiftTrader {
1616
///
1717
/// https://binance-docs.github.io/apidocs/spot/en/#new-order-trade
1818
///
19+
/// Notice: only MARKET orders are supported for the time being.
20+
///
1921
/// - Parameter orderInput: `BinanceSpotNewOrderParameters` instance that encapsulates
2022
/// all the arguments required for submiting the order.
2123
/// - Returns: An instance of `BinanceNewOrderResponse` or `SwiftTraderError`.
@@ -33,14 +35,6 @@ public extension SwiftTrader {
3335
guard let placeOrder = model as? BinanceSpotNewOrderResponse else {
3436
return .failure(.unexpectedResponse(modelString: "\(model)"))
3537
}
36-
#warning("TODO: how to handle cancelling order within Binance")
37-
// if orderInput.cancelStopOrders {
38-
// do {
39-
// try await kucoinSpotCancelStopOrders(symbol: orderInput.contractSymbol)
40-
// } catch {
41-
// logger.log("Could not cancel untriggered stop orders: \(error)")
42-
// }
43-
// }
4438
return .success(placeOrder)
4539
case .failure(let error):
4640
let swiftTraderError = handle(networkRequestError: error, operation: .binanceSpotNewOrder)

Sources/SwiftTrader/SwiftTrader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public struct SwiftTrader {
2222
// MARK: - Lifecycle
2323

2424
public init(binanceAuth: BinanceAuth,
25-
ftxAuth: FTXAuth?,
25+
ftxAuth: FTXAuth? = nil,
2626
kucoinAuth: KucoinAuth?,
2727
settings: SwiftTraderSettings = DefaultSwiftTraderSettings()) {
2828
self.binanceAuth = binanceAuth

0 commit comments

Comments
 (0)