@pipedream/bitmex 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/actions/create-order/create-order.mjs +168 -0
- package/actions/get-user-wallet/get-user-wallet.mjs +33 -0
- package/actions/list-trades/list-trades.mjs +103 -0
- package/bitmex.app.mjs +242 -5
- package/package.json +5 -1
- package/sources/common/common-polling.mjs +23 -0
- package/sources/new-executed-trade/new-executed-trade.mjs +127 -0
- package/sources/new-order/new-order.mjs +119 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import bitmex from "../../bitmex.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "bitmex-create-order",
|
|
5
|
+
name: "Create Order",
|
|
6
|
+
description: "Submit a new trading order in your BitMEX account. [See the documentation](https://www.bitmex.com/api/explorer/#!/Order/Order_new)",
|
|
7
|
+
version: "0.0.1",
|
|
8
|
+
annotations: {
|
|
9
|
+
destructiveHint: true,
|
|
10
|
+
openWorldHint: false,
|
|
11
|
+
readOnlyHint: false,
|
|
12
|
+
},
|
|
13
|
+
type: "action",
|
|
14
|
+
props: {
|
|
15
|
+
bitmex,
|
|
16
|
+
symbol: {
|
|
17
|
+
propDefinition: [
|
|
18
|
+
bitmex,
|
|
19
|
+
"symbol",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
strategy: {
|
|
23
|
+
type: "string",
|
|
24
|
+
label: "Strategy",
|
|
25
|
+
description: "Order strategy. e.g. 'OneWay', 'Long', 'Short'",
|
|
26
|
+
optional: true,
|
|
27
|
+
},
|
|
28
|
+
side: {
|
|
29
|
+
type: "string",
|
|
30
|
+
label: "Side",
|
|
31
|
+
description: "Order side. Valid options: Buy, Sell. Defaults to 'Buy' unless `orderQty` is negative",
|
|
32
|
+
optional: true,
|
|
33
|
+
options: [
|
|
34
|
+
"Buy",
|
|
35
|
+
"Sell",
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
orderQty: {
|
|
39
|
+
type: "integer",
|
|
40
|
+
label: "Order Quantity",
|
|
41
|
+
description: "Order quantity in units of the instrument (i.e. contracts, for spot it is base currency in minor currency for spot (e.g. XBt quantity for XBT))",
|
|
42
|
+
optional: true,
|
|
43
|
+
},
|
|
44
|
+
price: {
|
|
45
|
+
type: "string",
|
|
46
|
+
label: "Price",
|
|
47
|
+
description: "Optional limit price for 'Limit', 'StopLimit', and 'LimitIfTouched' orders",
|
|
48
|
+
optional: true,
|
|
49
|
+
},
|
|
50
|
+
displayQty: {
|
|
51
|
+
type: "integer",
|
|
52
|
+
label: "Display Quantity",
|
|
53
|
+
description: "Optional quantity to display in the book. Use 0 for a fully hidden order.",
|
|
54
|
+
optional: true,
|
|
55
|
+
},
|
|
56
|
+
stopPx: {
|
|
57
|
+
type: "string",
|
|
58
|
+
label: "Stop Price",
|
|
59
|
+
description: "Optional trigger price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders. Use a price below the current price for stop-sell orders and buy-if-touched orders. Use `execInst` of 'MarkPrice' or 'LastPrice' to define the current price used for triggering.",
|
|
60
|
+
optional: true,
|
|
61
|
+
},
|
|
62
|
+
clOrdID: {
|
|
63
|
+
type: "string",
|
|
64
|
+
label: "Client Order ID",
|
|
65
|
+
description: "Optional Client Order ID. This clOrdID will come back on the order and any related executions.",
|
|
66
|
+
optional: true,
|
|
67
|
+
},
|
|
68
|
+
clOrdLinkID: {
|
|
69
|
+
type: "string",
|
|
70
|
+
label: "Client Order Link ID",
|
|
71
|
+
description: "Optional Client Order Link ID for contingent orders",
|
|
72
|
+
optional: true,
|
|
73
|
+
},
|
|
74
|
+
pegOffsetValue: {
|
|
75
|
+
type: "string",
|
|
76
|
+
label: "Peg Offset Value",
|
|
77
|
+
description: "Optional trailing offset from the current price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders; use a negative offset for stop-sell orders and buy-if-touched orders. Optional offset from the peg price for 'Pegged' orders.",
|
|
78
|
+
optional: true,
|
|
79
|
+
},
|
|
80
|
+
pegPriceType: {
|
|
81
|
+
type: "string",
|
|
82
|
+
label: "Peg Price Type",
|
|
83
|
+
description: "Optional peg price type. Valid options: MarketPeg, PrimaryPeg, TrailingStopPeg",
|
|
84
|
+
optional: true,
|
|
85
|
+
options: [
|
|
86
|
+
"MarketPeg",
|
|
87
|
+
"PrimaryPeg",
|
|
88
|
+
"TrailingStopPeg",
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
ordType: {
|
|
92
|
+
type: "string",
|
|
93
|
+
label: "Order Type",
|
|
94
|
+
description: "Order type. Valid options: Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched, Pegged. Defaults to 'Limit' when `price` is specified. Defaults to 'Stop' when `stopPx` is specified. Defaults to 'StopLimit' when `price` and `stopPx` are specified.",
|
|
95
|
+
optional: true,
|
|
96
|
+
default: "Limit",
|
|
97
|
+
options: [
|
|
98
|
+
"Market",
|
|
99
|
+
"Limit",
|
|
100
|
+
"Stop",
|
|
101
|
+
"StopLimit",
|
|
102
|
+
"MarketIfTouched",
|
|
103
|
+
"LimitIfTouched",
|
|
104
|
+
"Pegged",
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
timeInForce: {
|
|
108
|
+
type: "string",
|
|
109
|
+
label: "Time In Force",
|
|
110
|
+
description: "Time in force. Valid options: Day, GoodTillCancel, ImmediateOrCancel, FillOrKill. Defaults to 'GoodTillCancel' for 'Limit', 'StopLimit', and 'LimitIfTouched' orders.",
|
|
111
|
+
optional: true,
|
|
112
|
+
options: [
|
|
113
|
+
"Day",
|
|
114
|
+
"GoodTillCancel",
|
|
115
|
+
"ImmediateOrCancel",
|
|
116
|
+
"FillOrKill",
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
execInst: {
|
|
120
|
+
type: "string",
|
|
121
|
+
label: "Execution Instructions",
|
|
122
|
+
description: "Optional execution instructions. Valid options: ParticipateDoNotInitiate, AllOrNone, MarkPrice, IndexPrice, LastPrice, Close, ReduceOnly, Fixed, LastWithinMark. 'AllOrNone' instruction requires `displayQty` to be 0. 'MarkPrice', 'IndexPrice' or 'LastPrice' instruction valid for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders. 'LastWithinMark' instruction valid for 'Stop' and 'StopLimit' with instruction 'LastPrice'. IndexPrice, LastWithMark, Close and ReduceOnly are not applicable to spot trading symbols.",
|
|
123
|
+
optional: true,
|
|
124
|
+
},
|
|
125
|
+
contingencyType: {
|
|
126
|
+
type: "string",
|
|
127
|
+
label: "Contingency Type",
|
|
128
|
+
description: "Optional contingency type for use with `clOrdLinkID`. Valid options: OneCancelsTheOther, OneTriggersTheOther.",
|
|
129
|
+
optional: true,
|
|
130
|
+
options: [
|
|
131
|
+
"OneCancelsTheOther",
|
|
132
|
+
"OneTriggersTheOther",
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
text: {
|
|
136
|
+
type: "string",
|
|
137
|
+
label: "Text",
|
|
138
|
+
description: "Optional order annotation. e.g. 'Take profit'",
|
|
139
|
+
optional: true,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
async run({ $ }) {
|
|
143
|
+
const response = await this.bitmex.createOrder({
|
|
144
|
+
symbol: this.symbol,
|
|
145
|
+
strategy: this.strategy,
|
|
146
|
+
side: this.side,
|
|
147
|
+
orderQty: this.orderQty,
|
|
148
|
+
price: this.price,
|
|
149
|
+
displayQty: this.displayQty,
|
|
150
|
+
stopPx: this.stopPx,
|
|
151
|
+
clOrdID: this.clOrdID,
|
|
152
|
+
clOrdLinkID: this.clOrdLinkID,
|
|
153
|
+
pegOffsetValue: this.pegOffsetValue,
|
|
154
|
+
pegPriceType: this.pegPriceType,
|
|
155
|
+
ordType: this.ordType,
|
|
156
|
+
timeInForce: this.timeInForce,
|
|
157
|
+
execInst: this.execInst,
|
|
158
|
+
contingencyType: this.contingencyType,
|
|
159
|
+
text: this.text,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
$.export("$summary", `Successfully created order${response.orderID
|
|
163
|
+
? ` with ID: ${response.orderID}`
|
|
164
|
+
: ""}`);
|
|
165
|
+
return response;
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import bitmex from "../../bitmex.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "bitmex-get-user-wallet",
|
|
5
|
+
name: "Get User Wallet",
|
|
6
|
+
description: "Retrieve your current wallet information from BitMEX. [See the documentation](https://www.bitmex.com/api/explorer/#!/User/User_getWallet)",
|
|
7
|
+
version: "0.0.1",
|
|
8
|
+
annotations: {
|
|
9
|
+
destructiveHint: false,
|
|
10
|
+
openWorldHint: false,
|
|
11
|
+
readOnlyHint: true,
|
|
12
|
+
},
|
|
13
|
+
type: "action",
|
|
14
|
+
props: {
|
|
15
|
+
bitmex,
|
|
16
|
+
currency: {
|
|
17
|
+
propDefinition: [
|
|
18
|
+
bitmex,
|
|
19
|
+
"currency",
|
|
20
|
+
],
|
|
21
|
+
description: "Any currency symbol, such as \"XBt\" or \"USDt\". For all currencies specify \"all\". Defaults to \"XBt\"",
|
|
22
|
+
default: "XBt",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
async run({ $ }) {
|
|
26
|
+
const response = await this.bitmex.getUserWallet({
|
|
27
|
+
currency: this.currency,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
$.export("$summary", `Successfully retrieved wallet information for currency: ${this.currency}`);
|
|
31
|
+
return response;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import bitmex from "../../bitmex.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "bitmex-list-trades",
|
|
5
|
+
name: "List Trades",
|
|
6
|
+
description: "Retrieve a list of executed trades from your BitMEX account. [See the documentation](https://www.bitmex.com/api/explorer/#!/Execution/Execution_getTradeHistory)",
|
|
7
|
+
version: "0.0.1",
|
|
8
|
+
annotations: {
|
|
9
|
+
destructiveHint: false,
|
|
10
|
+
openWorldHint: false,
|
|
11
|
+
readOnlyHint: true,
|
|
12
|
+
},
|
|
13
|
+
type: "action",
|
|
14
|
+
props: {
|
|
15
|
+
bitmex,
|
|
16
|
+
filter: {
|
|
17
|
+
type: "object",
|
|
18
|
+
label: "Filter",
|
|
19
|
+
description: "Generic table filter. Send JSON key/value pairs, such as `{\"execType\": [\"Settlement\", \"Trade\"]}` to filter on multiple values. For explanations on filters refer to http://www.onixs.biz/fix-dictionary/5.0.SP2/msgType_8_8.html",
|
|
20
|
+
optional: true,
|
|
21
|
+
},
|
|
22
|
+
symbol: {
|
|
23
|
+
propDefinition: [
|
|
24
|
+
bitmex,
|
|
25
|
+
"symbol",
|
|
26
|
+
],
|
|
27
|
+
optional: true,
|
|
28
|
+
},
|
|
29
|
+
columns: {
|
|
30
|
+
type: "string[]",
|
|
31
|
+
label: "Columns",
|
|
32
|
+
description: "Array of column names to fetch. If omitted, will return all columns. Note that this method will always return item keys, even when not specified, so you may receive more columns that you expect.",
|
|
33
|
+
optional: true,
|
|
34
|
+
},
|
|
35
|
+
count: {
|
|
36
|
+
type: "integer",
|
|
37
|
+
label: "Count",
|
|
38
|
+
description: "Number of results to fetch. Must be a positive integer. Defaults to 100",
|
|
39
|
+
optional: true,
|
|
40
|
+
default: 100,
|
|
41
|
+
},
|
|
42
|
+
start: {
|
|
43
|
+
type: "integer",
|
|
44
|
+
label: "Start",
|
|
45
|
+
description: "Starting point for results. Defaults to 0",
|
|
46
|
+
optional: true,
|
|
47
|
+
default: 0,
|
|
48
|
+
},
|
|
49
|
+
reverse: {
|
|
50
|
+
type: "boolean",
|
|
51
|
+
label: "Reverse",
|
|
52
|
+
description: "If `true`, will sort results newest first. Defaults to `false`",
|
|
53
|
+
optional: true,
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
startTime: {
|
|
57
|
+
type: "string",
|
|
58
|
+
label: "Start Time",
|
|
59
|
+
description: "Starting date filter for results (format: `YYYY-MM-DDTHH:mm:ss.sssZ`)",
|
|
60
|
+
optional: true,
|
|
61
|
+
},
|
|
62
|
+
endTime: {
|
|
63
|
+
type: "string",
|
|
64
|
+
label: "End Time",
|
|
65
|
+
description: "Ending date filter for results (format: `YYYY-MM-DDTHH:mm:ss.sssZ`)",
|
|
66
|
+
optional: true,
|
|
67
|
+
},
|
|
68
|
+
targetAccountId: {
|
|
69
|
+
type: "integer",
|
|
70
|
+
label: "Target Account ID",
|
|
71
|
+
description: "AccountId fetching the trade history, must be a paired account with main user",
|
|
72
|
+
optional: true,
|
|
73
|
+
},
|
|
74
|
+
targetAccountIds: {
|
|
75
|
+
type: "string",
|
|
76
|
+
label: "Target Account IDs",
|
|
77
|
+
description: "AccountIds fetching the trade history, must be a paired account with main user. Can be wildcard `*` to get all accounts linked to the authenticated user",
|
|
78
|
+
optional: true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
async run({ $ }) {
|
|
82
|
+
const response = await this.bitmex.getTradeHistory({
|
|
83
|
+
filter: this.filter,
|
|
84
|
+
symbol: this.symbol,
|
|
85
|
+
columns: this.columns,
|
|
86
|
+
count: this.count,
|
|
87
|
+
start: this.start,
|
|
88
|
+
reverse: this.reverse,
|
|
89
|
+
startTime: this.startTime,
|
|
90
|
+
endTime: this.endTime,
|
|
91
|
+
targetAccountId: this.targetAccountId,
|
|
92
|
+
targetAccountIds: this.targetAccountIds,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const count = Array.isArray(response)
|
|
96
|
+
? response.length
|
|
97
|
+
: 0;
|
|
98
|
+
$.export("$summary", `Successfully retrieved ${count} trade${count === 1
|
|
99
|
+
? ""
|
|
100
|
+
: "s"}`);
|
|
101
|
+
return response;
|
|
102
|
+
},
|
|
103
|
+
};
|
package/bitmex.app.mjs
CHANGED
|
@@ -1,11 +1,248 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
1
4
|
export default {
|
|
2
5
|
type: "app",
|
|
3
6
|
app: "bitmex",
|
|
4
|
-
propDefinitions: {
|
|
7
|
+
propDefinitions: {
|
|
8
|
+
currency: {
|
|
9
|
+
type: "string",
|
|
10
|
+
label: "Currency",
|
|
11
|
+
description: "Any currency. For all currencies specify \"all\"",
|
|
12
|
+
async options() {
|
|
13
|
+
const assets = await this.getAssetsConfig();
|
|
14
|
+
const currencies = assets.map((asset) => ({
|
|
15
|
+
label: asset.currency || asset.asset,
|
|
16
|
+
value: asset.currency || asset.asset,
|
|
17
|
+
}));
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
label: "All currencies",
|
|
21
|
+
value: "all",
|
|
22
|
+
},
|
|
23
|
+
...currencies,
|
|
24
|
+
];
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
symbol: {
|
|
28
|
+
type: "string",
|
|
29
|
+
label: "Symbol",
|
|
30
|
+
description: "Instrument symbol. Send a bare series (e.g. `XBT`) to get data for the nearest expiring contract in that series. You can also send a timeframe, e.g. `XBT:quarterly`. Timeframes are `nearest`, `daily`, `weekly`, `monthly`, `quarterly`, `biquarterly`, and `perpetual`. Symbols are case-insensitive.",
|
|
31
|
+
async options() {
|
|
32
|
+
const instruments = await this.getActiveAndIndices();
|
|
33
|
+
return instruments.map((instrument) => ({
|
|
34
|
+
label: `${instrument.symbol}${instrument.typ
|
|
35
|
+
? ` (${instrument.typ})`
|
|
36
|
+
: ""}`,
|
|
37
|
+
value: instrument.symbol,
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
5
42
|
methods: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
43
|
+
_apiUrl() {
|
|
44
|
+
return this.$auth.api_url || "https://www.bitmex.com";
|
|
45
|
+
},
|
|
46
|
+
_generateSignature(apiSecret, verb, path, expires, data) {
|
|
47
|
+
const message = verb + path + expires + data;
|
|
48
|
+
return crypto
|
|
49
|
+
.createHmac("sha256", apiSecret)
|
|
50
|
+
.update(message)
|
|
51
|
+
.digest("hex");
|
|
52
|
+
},
|
|
53
|
+
async _makeRequest({
|
|
54
|
+
method = "GET", path, params, data,
|
|
55
|
+
} = {}) {
|
|
56
|
+
const verb = method.toUpperCase();
|
|
57
|
+
const expires = Math.floor(Date.now() / 1000) + 60; // UNIX timestamp in seconds
|
|
58
|
+
|
|
59
|
+
// Build query string if params exist
|
|
60
|
+
let fullPath = path;
|
|
61
|
+
let queryString = "";
|
|
62
|
+
if (params && Object.keys(params).length > 0) {
|
|
63
|
+
const searchParams = new URLSearchParams();
|
|
64
|
+
Object.entries(params).forEach(([
|
|
65
|
+
key,
|
|
66
|
+
value,
|
|
67
|
+
]) => {
|
|
68
|
+
if (value != null) {
|
|
69
|
+
searchParams.append(key, value);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
queryString = searchParams.toString();
|
|
73
|
+
if (queryString) {
|
|
74
|
+
fullPath += `?${queryString}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle request body data
|
|
79
|
+
let dataString = "";
|
|
80
|
+
let requestData = null;
|
|
81
|
+
if (data && verb !== "GET") {
|
|
82
|
+
// For POST/PUT requests, convert data to JSON string
|
|
83
|
+
dataString = JSON.stringify(data);
|
|
84
|
+
requestData = data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const signature = this._generateSignature(
|
|
88
|
+
this.$auth.api_secret,
|
|
89
|
+
verb,
|
|
90
|
+
fullPath,
|
|
91
|
+
expires,
|
|
92
|
+
dataString,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const headers = {
|
|
96
|
+
"api-key": this.$auth.api_key,
|
|
97
|
+
"api-expires": expires,
|
|
98
|
+
"api-signature": signature,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const config = {
|
|
102
|
+
method: verb,
|
|
103
|
+
url: `${this._apiUrl()}${fullPath}`,
|
|
104
|
+
headers,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (requestData && verb !== "GET") {
|
|
108
|
+
config.data = requestData;
|
|
109
|
+
headers["Content-Type"] = "application/json";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const response = await axios(config);
|
|
113
|
+
return response.data;
|
|
114
|
+
},
|
|
115
|
+
async getAssetsConfig() {
|
|
116
|
+
return this._makeRequest({
|
|
117
|
+
method: "GET",
|
|
118
|
+
path: "/api/v1/wallet/assets",
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
async getActiveAndIndices() {
|
|
122
|
+
return this._makeRequest({
|
|
123
|
+
method: "GET",
|
|
124
|
+
path: "/api/v1/instrument/activeAndIndices",
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
async getUserWallet({ currency } = {}) {
|
|
128
|
+
return this._makeRequest({
|
|
129
|
+
method: "GET",
|
|
130
|
+
path: "/api/v1/user/wallet",
|
|
131
|
+
params: {
|
|
132
|
+
currency,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
async getTradeHistory({
|
|
137
|
+
filter, symbol, columns, count, start, reverse, startTime, endTime, targetAccountId,
|
|
138
|
+
targetAccountIds,
|
|
139
|
+
} = {}) {
|
|
140
|
+
const params = {};
|
|
141
|
+
if (filter != null) {
|
|
142
|
+
params.filter = typeof filter === "string"
|
|
143
|
+
? filter
|
|
144
|
+
: JSON.stringify(filter);
|
|
145
|
+
}
|
|
146
|
+
if (symbol != null) params.symbol = symbol;
|
|
147
|
+
if (columns != null) {
|
|
148
|
+
params.columns = typeof columns === "string"
|
|
149
|
+
? columns
|
|
150
|
+
: JSON.stringify(columns);
|
|
151
|
+
}
|
|
152
|
+
if (count != null) params.count = count.toString();
|
|
153
|
+
if (start != null) params.start = start.toString();
|
|
154
|
+
if (reverse != null) params.reverse = reverse;
|
|
155
|
+
if (startTime != null) params.startTime = startTime;
|
|
156
|
+
if (endTime != null) params.endTime = endTime;
|
|
157
|
+
if (targetAccountId != null) params.targetAccountId = targetAccountId.toString();
|
|
158
|
+
if (targetAccountIds != null) {
|
|
159
|
+
params.targetAccountIds = typeof targetAccountIds === "string"
|
|
160
|
+
? targetAccountIds
|
|
161
|
+
: JSON.stringify(targetAccountIds);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return this._makeRequest({
|
|
165
|
+
method: "GET",
|
|
166
|
+
path: "/api/v1/execution/tradeHistory",
|
|
167
|
+
params,
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
async createOrder({
|
|
171
|
+
symbol, strategy, side, orderQty, price, displayQty, stopPx, clOrdID, clOrdLinkID,
|
|
172
|
+
pegOffsetValue, pegPriceType, ordType, timeInForce, execInst, contingencyType, text,
|
|
173
|
+
} = {}) {
|
|
174
|
+
const data = {};
|
|
175
|
+
if (symbol != null) data.symbol = symbol;
|
|
176
|
+
if (strategy != null) data.strategy = strategy;
|
|
177
|
+
if (side != null) data.side = side;
|
|
178
|
+
if (orderQty != null) data.orderQty = orderQty.toString();
|
|
179
|
+
if (price != null) data.price = price.toString();
|
|
180
|
+
if (displayQty != null) data.displayQty = displayQty.toString();
|
|
181
|
+
if (stopPx != null) data.stopPx = stopPx.toString();
|
|
182
|
+
if (clOrdID != null) data.clOrdID = clOrdID;
|
|
183
|
+
if (clOrdLinkID != null) data.clOrdLinkID = clOrdLinkID;
|
|
184
|
+
if (pegOffsetValue != null) data.pegOffsetValue = pegOffsetValue.toString();
|
|
185
|
+
if (pegPriceType != null) data.pegPriceType = pegPriceType;
|
|
186
|
+
if (ordType != null) data.ordType = ordType;
|
|
187
|
+
if (timeInForce != null) data.timeInForce = timeInForce;
|
|
188
|
+
if (execInst != null) data.execInst = execInst;
|
|
189
|
+
if (contingencyType != null) data.contingencyType = contingencyType;
|
|
190
|
+
if (text != null) data.text = text;
|
|
191
|
+
|
|
192
|
+
return this._makeRequest({
|
|
193
|
+
method: "POST",
|
|
194
|
+
path: "/api/v1/order",
|
|
195
|
+
data,
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
async getOrders({
|
|
199
|
+
symbol, filter, columns, count, start, reverse, startTime, endTime,
|
|
200
|
+
} = {}) {
|
|
201
|
+
const params = {};
|
|
202
|
+
if (symbol != null) params.symbol = symbol;
|
|
203
|
+
if (filter != null) {
|
|
204
|
+
params.filter = typeof filter === "string"
|
|
205
|
+
? filter
|
|
206
|
+
: JSON.stringify(filter);
|
|
207
|
+
}
|
|
208
|
+
if (columns != null) {
|
|
209
|
+
params.columns = typeof columns === "string"
|
|
210
|
+
? columns
|
|
211
|
+
: JSON.stringify(columns);
|
|
212
|
+
}
|
|
213
|
+
if (count != null) params.count = count.toString();
|
|
214
|
+
if (start != null) params.start = start.toString();
|
|
215
|
+
if (reverse != null) params.reverse = reverse;
|
|
216
|
+
if (startTime != null) params.startTime = startTime;
|
|
217
|
+
if (endTime != null) params.endTime = endTime;
|
|
218
|
+
|
|
219
|
+
return this._makeRequest({
|
|
220
|
+
method: "GET",
|
|
221
|
+
path: "/api/v1/order",
|
|
222
|
+
params,
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
async getPositions({
|
|
226
|
+
filter, columns, count,
|
|
227
|
+
} = {}) {
|
|
228
|
+
const params = {};
|
|
229
|
+
if (filter != null) {
|
|
230
|
+
params.filter = typeof filter === "string"
|
|
231
|
+
? filter
|
|
232
|
+
: JSON.stringify(filter);
|
|
233
|
+
}
|
|
234
|
+
if (columns != null) {
|
|
235
|
+
params.columns = typeof columns === "string"
|
|
236
|
+
? columns
|
|
237
|
+
: JSON.stringify(columns);
|
|
238
|
+
}
|
|
239
|
+
if (count != null) params.count = count.toString();
|
|
240
|
+
|
|
241
|
+
return this._makeRequest({
|
|
242
|
+
method: "GET",
|
|
243
|
+
path: "/api/v1/position",
|
|
244
|
+
params,
|
|
245
|
+
});
|
|
9
246
|
},
|
|
10
247
|
},
|
|
11
|
-
};
|
|
248
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipedream/bitmex",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Pipedream BitMEX Components",
|
|
5
5
|
"main": "bitmex.app.mjs",
|
|
6
6
|
"keywords": [
|
|
@@ -11,5 +11,9 @@
|
|
|
11
11
|
"author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@pipedream/platform": "^3.1.1",
|
|
17
|
+
"axios": "^1.6.0"
|
|
14
18
|
}
|
|
15
19
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import bitmex from "../../bitmex.app.mjs";
|
|
2
|
+
import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
props: {
|
|
6
|
+
bitmex,
|
|
7
|
+
db: "$.service.db",
|
|
8
|
+
timer: {
|
|
9
|
+
type: "$.interface.timer",
|
|
10
|
+
default: {
|
|
11
|
+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
methods: {
|
|
16
|
+
_getLastTimestamp() {
|
|
17
|
+
return this.db.get("lastTimestamp");
|
|
18
|
+
},
|
|
19
|
+
_setLastTimestamp(timestamp) {
|
|
20
|
+
this.db.set("lastTimestamp", timestamp);
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import common from "../common/common-polling.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
...common,
|
|
5
|
+
key: "bitmex-new-executed-trade",
|
|
6
|
+
name: "New Executed Trade",
|
|
7
|
+
description: "Emit new event when a balance‑affecting execution (trade fill, settlement, realized PnL) occurs in your BitMEX account. [See the documentation](https://www.bitmex.com/api/explorer/#!/Execution/Execution_getTradeHistory)",
|
|
8
|
+
version: "0.0.1",
|
|
9
|
+
type: "source",
|
|
10
|
+
dedupe: "unique",
|
|
11
|
+
props: {
|
|
12
|
+
...common.props,
|
|
13
|
+
targetAccountId: {
|
|
14
|
+
type: "integer",
|
|
15
|
+
label: "Target Account ID",
|
|
16
|
+
description: "AccountId fetching the trade history, must be a paired account with main user",
|
|
17
|
+
},
|
|
18
|
+
symbol: {
|
|
19
|
+
propDefinition: [
|
|
20
|
+
common.props.bitmex,
|
|
21
|
+
"symbol",
|
|
22
|
+
],
|
|
23
|
+
optional: true,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
methods: {
|
|
27
|
+
...common.methods,
|
|
28
|
+
_getEmittedTradeIds() {
|
|
29
|
+
return new Set(this.db.get("emittedTradeIds") || []);
|
|
30
|
+
},
|
|
31
|
+
_setEmittedTradeIds(tradeIds) {
|
|
32
|
+
this.db.set("emittedTradeIds", Array.from(tradeIds));
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
hooks: {
|
|
36
|
+
async deploy() {
|
|
37
|
+
const trades = await this.bitmex.getTradeHistory({
|
|
38
|
+
targetAccountId: this.targetAccountId,
|
|
39
|
+
symbol: this.symbol,
|
|
40
|
+
count: 25,
|
|
41
|
+
reverse: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const emittedTradeIds = new Set();
|
|
45
|
+
for (const trade of trades.slice(0, 10)) {
|
|
46
|
+
if (trade.execID) {
|
|
47
|
+
emittedTradeIds.add(trade.execID);
|
|
48
|
+
this.$emit(trade, {
|
|
49
|
+
id: trade.execID,
|
|
50
|
+
summary: `Executed trade: ${trade.symbol} ${trade.side || ""} ${trade.lastQty || 0} @ ${trade.lastPx || 0}`,
|
|
51
|
+
ts: trade.timestamp
|
|
52
|
+
? new Date(trade.timestamp).getTime()
|
|
53
|
+
: Date.now(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this._setEmittedTradeIds(emittedTradeIds);
|
|
58
|
+
if (trades.length > 0 && trades[0].timestamp) {
|
|
59
|
+
this._setLastTimestamp(new Date(trades[0].timestamp).getTime());
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
async run() {
|
|
64
|
+
const lastTimestamp = this._getLastTimestamp();
|
|
65
|
+
const emittedTradeIds = this._getEmittedTradeIds();
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
|
|
68
|
+
const trades = await this.bitmex.getTradeHistory({
|
|
69
|
+
targetAccountId: this.targetAccountId,
|
|
70
|
+
symbol: this.symbol,
|
|
71
|
+
count: 100,
|
|
72
|
+
reverse: true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const newTrades = [];
|
|
76
|
+
for (const trade of trades) {
|
|
77
|
+
if (!trade.execID || emittedTradeIds.has(trade.execID)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const tradeTimestamp = trade.timestamp
|
|
82
|
+
? new Date(trade.timestamp).getTime()
|
|
83
|
+
: now;
|
|
84
|
+
if (!lastTimestamp || tradeTimestamp > lastTimestamp) {
|
|
85
|
+
newTrades.push(trade);
|
|
86
|
+
emittedTradeIds.add(trade.execID);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Sort by timestamp ascending
|
|
91
|
+
newTrades.sort((a, b) => {
|
|
92
|
+
const tsA = a.timestamp
|
|
93
|
+
? new Date(a.timestamp).getTime()
|
|
94
|
+
: 0;
|
|
95
|
+
const tsB = b.timestamp
|
|
96
|
+
? new Date(b.timestamp).getTime()
|
|
97
|
+
: 0;
|
|
98
|
+
return tsA - tsB;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
for (const trade of newTrades) {
|
|
102
|
+
this.$emit(trade, {
|
|
103
|
+
id: trade.execID,
|
|
104
|
+
summary: `Executed trade: ${trade.symbol} ${trade.side || ""} ${trade.lastQty || 0} @ ${trade.lastPx || 0}`,
|
|
105
|
+
ts: trade.timestamp
|
|
106
|
+
? new Date(trade.timestamp).getTime()
|
|
107
|
+
: Date.now(),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (newTrades.length > 0) {
|
|
112
|
+
const latestTimestamp = newTrades[newTrades.length - 1].timestamp
|
|
113
|
+
? new Date(newTrades[newTrades.length - 1].timestamp).getTime()
|
|
114
|
+
: now;
|
|
115
|
+
this._setLastTimestamp(latestTimestamp);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Keep only recent trade IDs (last 1000)
|
|
119
|
+
const tradeIdsArray = Array.from(emittedTradeIds);
|
|
120
|
+
if (tradeIdsArray.length > 1000) {
|
|
121
|
+
this._setEmittedTradeIds(new Set(tradeIdsArray.slice(-1000)));
|
|
122
|
+
} else {
|
|
123
|
+
this._setEmittedTradeIds(emittedTradeIds);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import common from "../common/common-polling.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
...common,
|
|
5
|
+
key: "bitmex-new-order",
|
|
6
|
+
name: "New Order",
|
|
7
|
+
description: "Emit new event when a new order is placed on your BitMEX account. [See the documentation](https://www.bitmex.com/api/explorer/#!/Order/Order_getOrders)",
|
|
8
|
+
version: "0.0.1",
|
|
9
|
+
type: "source",
|
|
10
|
+
dedupe: "unique",
|
|
11
|
+
props: {
|
|
12
|
+
...common.props,
|
|
13
|
+
symbol: {
|
|
14
|
+
propDefinition: [
|
|
15
|
+
common.props.bitmex,
|
|
16
|
+
"symbol",
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
methods: {
|
|
21
|
+
...common.methods,
|
|
22
|
+
_getEmittedOrderIds() {
|
|
23
|
+
return new Set(this.db.get("emittedOrderIds") || []);
|
|
24
|
+
},
|
|
25
|
+
_setEmittedOrderIds(orderIds) {
|
|
26
|
+
this.db.set("emittedOrderIds", Array.from(orderIds));
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
hooks: {
|
|
30
|
+
async deploy() {
|
|
31
|
+
const orders = await this.bitmex.getOrders({
|
|
32
|
+
symbol: this.symbol,
|
|
33
|
+
count: 25,
|
|
34
|
+
reverse: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const emittedOrderIds = new Set();
|
|
38
|
+
for (const order of orders.slice(0, 10)) {
|
|
39
|
+
if (order.orderID) {
|
|
40
|
+
emittedOrderIds.add(order.orderID);
|
|
41
|
+
this.$emit(order, {
|
|
42
|
+
id: order.orderID,
|
|
43
|
+
summary: `New order: ${order.symbol} ${order.side} ${order.orderQty || 0}`,
|
|
44
|
+
ts: order.timestamp
|
|
45
|
+
? new Date(order.timestamp).getTime()
|
|
46
|
+
: Date.now(),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
this._setEmittedOrderIds(emittedOrderIds);
|
|
51
|
+
if (orders.length > 0 && orders[0].timestamp) {
|
|
52
|
+
this._setLastTimestamp(new Date(orders[0].timestamp).getTime());
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
async run() {
|
|
57
|
+
const lastTimestamp = this._getLastTimestamp();
|
|
58
|
+
const emittedOrderIds = this._getEmittedOrderIds();
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
|
|
61
|
+
const orders = await this.bitmex.getOrders({
|
|
62
|
+
symbol: this.symbol,
|
|
63
|
+
count: 100,
|
|
64
|
+
reverse: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const newOrders = [];
|
|
68
|
+
for (const order of orders) {
|
|
69
|
+
if (!order.orderID || emittedOrderIds.has(order.orderID)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const orderTimestamp = order.timestamp
|
|
74
|
+
? new Date(order.timestamp).getTime()
|
|
75
|
+
: now;
|
|
76
|
+
if (!lastTimestamp || orderTimestamp > lastTimestamp) {
|
|
77
|
+
newOrders.push(order);
|
|
78
|
+
emittedOrderIds.add(order.orderID);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sort by timestamp ascending
|
|
83
|
+
newOrders.sort((a, b) => {
|
|
84
|
+
const tsA = a.timestamp
|
|
85
|
+
? new Date(a.timestamp).getTime()
|
|
86
|
+
: 0;
|
|
87
|
+
const tsB = b.timestamp
|
|
88
|
+
? new Date(b.timestamp).getTime()
|
|
89
|
+
: 0;
|
|
90
|
+
return tsA - tsB;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
for (const order of newOrders) {
|
|
94
|
+
this.$emit(order, {
|
|
95
|
+
id: order.orderID,
|
|
96
|
+
summary: `New order: ${order.symbol} ${order.side} ${order.orderQty || 0}`,
|
|
97
|
+
ts: order.timestamp
|
|
98
|
+
? new Date(order.timestamp).getTime()
|
|
99
|
+
: Date.now(),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (newOrders.length > 0) {
|
|
104
|
+
const latestTimestamp = newOrders[newOrders.length - 1].timestamp
|
|
105
|
+
? new Date(newOrders[newOrders.length - 1].timestamp).getTime()
|
|
106
|
+
: now;
|
|
107
|
+
this._setLastTimestamp(latestTimestamp);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Keep only recent order IDs (last 1000)
|
|
111
|
+
const orderIdsArray = Array.from(emittedOrderIds);
|
|
112
|
+
if (orderIdsArray.length > 1000) {
|
|
113
|
+
this._setEmittedOrderIds(new Set(orderIdsArray.slice(-1000)));
|
|
114
|
+
} else {
|
|
115
|
+
this._setEmittedOrderIds(emittedOrderIds);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|