@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.
@@ -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
- // this.$auth contains connected account data
7
- authKeys() {
8
- console.log(Object.keys(this.$auth));
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.1",
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
+