ctrader-x 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/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +23 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/spotware-oauth-client.d.ts +31 -0
- package/dist/auth/spotware-oauth-client.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-client.js +65 -0
- package/dist/auth/spotware-oauth-client.js.map +1 -0
- package/dist/auth/spotware-oauth-endpoints.d.ts +3 -0
- package/dist/auth/spotware-oauth-endpoints.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-endpoints.js +8 -0
- package/dist/auth/spotware-oauth-endpoints.js.map +1 -0
- package/dist/auth/spotware-oauth-error.d.ts +6 -0
- package/dist/auth/spotware-oauth-error.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-error.js +15 -0
- package/dist/auth/spotware-oauth-error.js.map +1 -0
- package/dist/auth/spotware-oauth-scope.enum.d.ts +5 -0
- package/dist/auth/spotware-oauth-scope.enum.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-scope.enum.js +9 -0
- package/dist/auth/spotware-oauth-scope.enum.js.map +1 -0
- package/dist/auth/spotware-oauth-token.model.d.ts +7 -0
- package/dist/auth/spotware-oauth-token.model.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-token.model.js +3 -0
- package/dist/auth/spotware-oauth-token.model.js.map +1 -0
- package/dist/auth/spotware-socket-auth-error.d.ts +5 -0
- package/dist/auth/spotware-socket-auth-error.d.ts.map +1 -0
- package/dist/auth/spotware-socket-auth-error.js +13 -0
- package/dist/auth/spotware-socket-auth-error.js.map +1 -0
- package/dist/auth/spotware-socket-authenticator.d.ts +21 -0
- package/dist/auth/spotware-socket-authenticator.d.ts.map +1 -0
- package/dist/auth/spotware-socket-authenticator.js +80 -0
- package/dist/auth/spotware-socket-authenticator.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +19 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/spotware-client.d.ts +56 -0
- package/dist/client/spotware-client.d.ts.map +1 -0
- package/dist/client/spotware-client.js +172 -0
- package/dist/client/spotware-client.js.map +1 -0
- package/dist/client/spotware-request-error.d.ts +5 -0
- package/dist/client/spotware-request-error.d.ts.map +1 -0
- package/dist/client/spotware-request-error.js +13 -0
- package/dist/client/spotware-request-error.js.map +1 -0
- package/dist/example/example.d.ts +2 -0
- package/dist/example/example.d.ts.map +1 -0
- package/dist/example/example.js +187 -0
- package/dist/example/example.js.map +1 -0
- package/dist/example/market-data-example.d.ts +2 -0
- package/dist/example/market-data-example.d.ts.map +1 -0
- package/dist/example/market-data-example.js +32 -0
- package/dist/example/market-data-example.js.map +1 -0
- package/dist/example/shared/create-authenticated-client.d.ts +9 -0
- package/dist/example/shared/create-authenticated-client.d.ts.map +1 -0
- package/dist/example/shared/create-authenticated-client.js +154 -0
- package/dist/example/shared/create-authenticated-client.js.map +1 -0
- package/dist/example/trading-example.d.ts +2 -0
- package/dist/example/trading-example.d.ts.map +1 -0
- package/dist/example/trading-example.js +78 -0
- package/dist/example/trading-example.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/market-data/index.d.ts +3 -0
- package/dist/market-data/index.d.ts.map +1 -0
- package/dist/market-data/index.js +19 -0
- package/dist/market-data/index.js.map +1 -0
- package/dist/market-data/spotware-market-data.d.ts +31 -0
- package/dist/market-data/spotware-market-data.d.ts.map +1 -0
- package/dist/market-data/spotware-market-data.js +68 -0
- package/dist/market-data/spotware-market-data.js.map +1 -0
- package/dist/market-data/spotware-symbol-catalog.d.ts +19 -0
- package/dist/market-data/spotware-symbol-catalog.d.ts.map +1 -0
- package/dist/market-data/spotware-symbol-catalog.js +52 -0
- package/dist/market-data/spotware-symbol-catalog.js.map +1 -0
- package/dist/shared/sleep.d.ts +5 -0
- package/dist/shared/sleep.d.ts.map +1 -0
- package/dist/shared/sleep.js +17 -0
- package/dist/shared/sleep.js.map +1 -0
- package/dist/shared/spotware-scale.d.ts +3 -0
- package/dist/shared/spotware-scale.d.ts.map +1 -0
- package/dist/shared/spotware-scale.js +12 -0
- package/dist/shared/spotware-scale.js.map +1 -0
- package/dist/shared/typed-event-emitter.d.ts +13 -0
- package/dist/shared/typed-event-emitter.d.ts.map +1 -0
- package/dist/shared/typed-event-emitter.js +28 -0
- package/dist/shared/typed-event-emitter.js.map +1 -0
- package/dist/trading/index.d.ts +2 -0
- package/dist/trading/index.d.ts.map +1 -0
- package/dist/trading/index.js +18 -0
- package/dist/trading/index.js.map +1 -0
- package/dist/trading/spotware-trading.d.ts +59 -0
- package/dist/trading/spotware-trading.d.ts.map +1 -0
- package/dist/trading/spotware-trading.js +97 -0
- package/dist/trading/spotware-trading.js.map +1 -0
- package/dist/transport/frame-codec.d.ts +6 -0
- package/dist/transport/frame-codec.d.ts.map +1 -0
- package/dist/transport/frame-codec.js +35 -0
- package/dist/transport/frame-codec.js.map +1 -0
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +20 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/rate-limiter.d.ts +11 -0
- package/dist/transport/rate-limiter.d.ts.map +1 -0
- package/dist/transport/rate-limiter.js +48 -0
- package/dist/transport/rate-limiter.js.map +1 -0
- package/dist/transport/reconnect-backoff.d.ts +12 -0
- package/dist/transport/reconnect-backoff.d.ts.map +1 -0
- package/dist/transport/reconnect-backoff.js +20 -0
- package/dist/transport/reconnect-backoff.js.map +1 -0
- package/dist/transport/spotware-host.enum.d.ts +6 -0
- package/dist/transport/spotware-host.enum.d.ts.map +1 -0
- package/dist/transport/spotware-host.enum.js +10 -0
- package/dist/transport/spotware-host.enum.js.map +1 -0
- package/dist/transport/spotware-rate-limiter.d.ts +8 -0
- package/dist/transport/spotware-rate-limiter.d.ts.map +1 -0
- package/dist/transport/spotware-rate-limiter.js +29 -0
- package/dist/transport/spotware-rate-limiter.js.map +1 -0
- package/dist/transport/spotware-transport.d.ts +63 -0
- package/dist/transport/spotware-transport.d.ts.map +1 -0
- package/dist/transport/spotware-transport.js +237 -0
- package/dist/transport/spotware-transport.js.map +1 -0
- package/dist/types/generated/OpenApiCommonMessages.d.ts +49 -0
- package/dist/types/generated/OpenApiCommonMessages.d.ts.map +1 -0
- package/dist/types/generated/OpenApiCommonMessages.js +285 -0
- package/dist/types/generated/OpenApiCommonMessages.js.map +1 -0
- package/dist/types/generated/OpenApiCommonModelMessages.d.ts +40 -0
- package/dist/types/generated/OpenApiCommonModelMessages.d.ts.map +1 -0
- package/dist/types/generated/OpenApiCommonModelMessages.js +140 -0
- package/dist/types/generated/OpenApiCommonModelMessages.js.map +1 -0
- package/dist/types/generated/OpenApiMessages.d.ts +964 -0
- package/dist/types/generated/OpenApiMessages.d.ts.map +1 -0
- package/dist/types/generated/OpenApiMessages.js +8754 -0
- package/dist/types/generated/OpenApiMessages.js.map +1 -0
- package/dist/types/generated/OpenApiModelMessages.d.ts +1116 -0
- package/dist/types/generated/OpenApiModelMessages.d.ts.map +1 -0
- package/dist/types/generated/OpenApiModelMessages.js +6917 -0
- package/dist/types/generated/OpenApiModelMessages.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +141 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TypedEventEmitter = void 0;
|
|
4
|
+
const node_events_1 = require("node:events");
|
|
5
|
+
/**
|
|
6
|
+
* Wraps Node's EventEmitter instead of extending it, so `emit` stays protected:
|
|
7
|
+
* consumers can only listen, only the owning class can trigger events.
|
|
8
|
+
*/
|
|
9
|
+
class TypedEventEmitter {
|
|
10
|
+
emitter = new node_events_1.EventEmitter();
|
|
11
|
+
on(event, listener) {
|
|
12
|
+
this.emitter.on(event, listener);
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
once(event, listener) {
|
|
16
|
+
this.emitter.once(event, listener);
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
off(event, listener) {
|
|
20
|
+
this.emitter.off(event, listener);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
emit(event, ...args) {
|
|
24
|
+
return this.emitter.emit(event, ...args);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.TypedEventEmitter = TypedEventEmitter;
|
|
28
|
+
//# sourceMappingURL=typed-event-emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typed-event-emitter.js","sourceRoot":"","sources":["../../src/shared/typed-event-emitter.ts"],"names":[],"mappings":";;;AAAA,6CAA2C;AAI3C;;;GAGG;AACH,MAAa,iBAAiB;IACT,OAAO,GAAG,IAAI,0BAAY,EAAE,CAAC;IAE9C,EAAE,CAAwC,KAAa,EAAE,QAA4C;QACjG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAwC,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,IAAI,CAAwC,KAAa,EAAE,QAA4C;QACnG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,QAAwC,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,GAAG,CAAwC,KAAa,EAAE,QAA4C;QAClG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAwC,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC;IAChB,CAAC;IAES,IAAI,CAAwC,KAAa,EAAE,GAAG,IAAqB;QACzF,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;IAC7C,CAAC;CACJ;AArBD,8CAqBC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/trading/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./spotware-trading"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/trading/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { SpotwareClient } from '../client';
|
|
2
|
+
import { ProtoOAExecutionEvent, ProtoOAOrder, ProtoOAPosition, ProtoOATimeInForce, ProtoOATradeSide } from '../types';
|
|
3
|
+
export interface IPlaceMarketOrderParams {
|
|
4
|
+
symbolId: number;
|
|
5
|
+
tradeSide: ProtoOATradeSide;
|
|
6
|
+
/** In units (e.g. 1000 for 1000 units) — converted internally, not the wire's cents form. */
|
|
7
|
+
volume: number;
|
|
8
|
+
/** Absolute price (e.g. 1.23456), not scaled. */
|
|
9
|
+
stopLoss?: number;
|
|
10
|
+
/** Absolute price (e.g. 1.23456), not scaled. */
|
|
11
|
+
takeProfit?: number;
|
|
12
|
+
comment?: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface IPlaceLimitOrderParams extends IPlaceMarketOrderParams {
|
|
16
|
+
/** Absolute price (e.g. 1.23456), not scaled. */
|
|
17
|
+
limitPrice: number;
|
|
18
|
+
timeInForce?: ProtoOATimeInForce;
|
|
19
|
+
expirationTimestamp?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface IAmendOrderParams {
|
|
22
|
+
orderId: number;
|
|
23
|
+
/** In units — converted internally, not the wire's cents form. */
|
|
24
|
+
volume?: number;
|
|
25
|
+
limitPrice?: number;
|
|
26
|
+
stopPrice?: number;
|
|
27
|
+
stopLoss?: number;
|
|
28
|
+
takeProfit?: number;
|
|
29
|
+
expirationTimestamp?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface IClosePositionParams {
|
|
32
|
+
positionId: number;
|
|
33
|
+
/** In units — converted internally, not the wire's cents form. */
|
|
34
|
+
volume: number;
|
|
35
|
+
}
|
|
36
|
+
export interface IOpenPositionsAndOrders {
|
|
37
|
+
positions: ProtoOAPosition[];
|
|
38
|
+
orders: ProtoOAOrder[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Places/modifies/cancels orders and closes positions, via `client`. Knows nothing about
|
|
42
|
+
* market data streaming. Order mutations have no dedicated response message: the outcome
|
|
43
|
+
* arrives as a ProtoOAExecutionEvent on success, or rejects via ProtoOAOrderErrorEvent (which
|
|
44
|
+
* `client` already treats as an error) on failure. Volume here is in units, not the wire's
|
|
45
|
+
* cents representation; prices other than relative SL/TP stay plain decimals, matching the
|
|
46
|
+
* protocol's own convention (confirmed against Spotware's official SDK, not assumed).
|
|
47
|
+
*/
|
|
48
|
+
export declare class SpotwareTrading {
|
|
49
|
+
private readonly client;
|
|
50
|
+
constructor(client: SpotwareClient);
|
|
51
|
+
placeMarketOrder(params: IPlaceMarketOrderParams): Promise<ProtoOAExecutionEvent>;
|
|
52
|
+
placeLimitOrder(params: IPlaceLimitOrderParams): Promise<ProtoOAExecutionEvent>;
|
|
53
|
+
amendOrder(params: IAmendOrderParams): Promise<ProtoOAExecutionEvent>;
|
|
54
|
+
cancelOrder(orderId: number): Promise<ProtoOAExecutionEvent>;
|
|
55
|
+
closePosition(params: IClosePositionParams): Promise<ProtoOAExecutionEvent>;
|
|
56
|
+
getOpenPositionsAndOrders(): Promise<IOpenPositionsAndOrders>;
|
|
57
|
+
private placeOrder;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=spotware-trading.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-trading.d.ts","sourceRoot":"","sources":["../../src/trading/spotware-trading.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAIH,qBAAqB,EAErB,YAAY,EAGZ,eAAe,EAGf,kBAAkB,EAClB,gBAAgB,EACnB,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,6FAA6F;IAC7F,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;IACnE,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACpC,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,MAAM,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,qBAAa,eAAe;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAEnD,gBAAgB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAIjF,eAAe,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAIzE,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAiBrE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAO5D,aAAa,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAe3E,yBAAyB,IAAI,OAAO,CAAC,uBAAuB,CAAC;YAQrD,UAAU;CAsB3B"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotwareTrading = void 0;
|
|
4
|
+
const spotware_scale_1 = require("../shared/spotware-scale");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
/**
|
|
7
|
+
* Places/modifies/cancels orders and closes positions, via `client`. Knows nothing about
|
|
8
|
+
* market data streaming. Order mutations have no dedicated response message: the outcome
|
|
9
|
+
* arrives as a ProtoOAExecutionEvent on success, or rejects via ProtoOAOrderErrorEvent (which
|
|
10
|
+
* `client` already treats as an error) on failure. Volume here is in units, not the wire's
|
|
11
|
+
* cents representation; prices other than relative SL/TP stay plain decimals, matching the
|
|
12
|
+
* protocol's own convention (confirmed against Spotware's official SDK, not assumed).
|
|
13
|
+
*/
|
|
14
|
+
class SpotwareTrading {
|
|
15
|
+
client;
|
|
16
|
+
constructor(client) {
|
|
17
|
+
this.client = client;
|
|
18
|
+
}
|
|
19
|
+
placeMarketOrder(params) {
|
|
20
|
+
return this.placeOrder({ ...params, orderType: types_1.ProtoOAOrderType.MARKET });
|
|
21
|
+
}
|
|
22
|
+
placeLimitOrder(params) {
|
|
23
|
+
return this.placeOrder({ ...params, orderType: types_1.ProtoOAOrderType.LIMIT });
|
|
24
|
+
}
|
|
25
|
+
async amendOrder(params) {
|
|
26
|
+
const request = types_1.ProtoOAAmendOrderReq.fromPartial({
|
|
27
|
+
ctidTraderAccountId: this.client.ctidTraderAccountId,
|
|
28
|
+
orderId: params.orderId,
|
|
29
|
+
volume: params.volume === undefined ? undefined : params.volume * spotware_scale_1.SPOTWARE_VOLUME_SCALE,
|
|
30
|
+
limitPrice: params.limitPrice,
|
|
31
|
+
stopPrice: params.stopPrice,
|
|
32
|
+
stopLoss: params.stopLoss,
|
|
33
|
+
takeProfit: params.takeProfit,
|
|
34
|
+
expirationTimestamp: params.expirationTimestamp
|
|
35
|
+
});
|
|
36
|
+
const response = await this.client.send(types_1.ProtoOAPayloadType.PROTO_OA_AMEND_ORDER_REQ, types_1.ProtoOAAmendOrderReq.encode(request).finish());
|
|
37
|
+
return types_1.ProtoOAExecutionEvent.decode(response.payload ?? new Uint8Array());
|
|
38
|
+
}
|
|
39
|
+
async cancelOrder(orderId) {
|
|
40
|
+
const request = types_1.ProtoOACancelOrderReq.fromPartial({ ctidTraderAccountId: this.client.ctidTraderAccountId, orderId });
|
|
41
|
+
const response = await this.client.send(types_1.ProtoOAPayloadType.PROTO_OA_CANCEL_ORDER_REQ, types_1.ProtoOACancelOrderReq.encode(request).finish());
|
|
42
|
+
return types_1.ProtoOAExecutionEvent.decode(response.payload ?? new Uint8Array());
|
|
43
|
+
}
|
|
44
|
+
async closePosition(params) {
|
|
45
|
+
const request = types_1.ProtoOAClosePositionReq.fromPartial({
|
|
46
|
+
ctidTraderAccountId: this.client.ctidTraderAccountId,
|
|
47
|
+
positionId: params.positionId,
|
|
48
|
+
volume: params.volume * spotware_scale_1.SPOTWARE_VOLUME_SCALE
|
|
49
|
+
});
|
|
50
|
+
const response = await this.client.send(types_1.ProtoOAPayloadType.PROTO_OA_CLOSE_POSITION_REQ, types_1.ProtoOAClosePositionReq.encode(request).finish());
|
|
51
|
+
return types_1.ProtoOAExecutionEvent.decode(response.payload ?? new Uint8Array());
|
|
52
|
+
}
|
|
53
|
+
async getOpenPositionsAndOrders() {
|
|
54
|
+
const request = types_1.ProtoOAReconcileReq.fromPartial({ ctidTraderAccountId: this.client.ctidTraderAccountId });
|
|
55
|
+
const response = await this.client.send(types_1.ProtoOAPayloadType.PROTO_OA_RECONCILE_REQ, types_1.ProtoOAReconcileReq.encode(request).finish());
|
|
56
|
+
const reconcile = types_1.ProtoOAReconcileRes.decode(response.payload ?? new Uint8Array());
|
|
57
|
+
return { positions: reconcile.position, orders: reconcile.order };
|
|
58
|
+
}
|
|
59
|
+
async placeOrder(params) {
|
|
60
|
+
const request = types_1.ProtoOANewOrderReq.fromPartial({
|
|
61
|
+
ctidTraderAccountId: this.client.ctidTraderAccountId,
|
|
62
|
+
symbolId: params.symbolId,
|
|
63
|
+
orderType: params.orderType,
|
|
64
|
+
tradeSide: params.tradeSide,
|
|
65
|
+
volume: params.volume * spotware_scale_1.SPOTWARE_VOLUME_SCALE,
|
|
66
|
+
limitPrice: 'limitPrice' in params ? params.limitPrice : undefined,
|
|
67
|
+
timeInForce: 'timeInForce' in params ? params.timeInForce : undefined,
|
|
68
|
+
expirationTimestamp: 'expirationTimestamp' in params ? params.expirationTimestamp : undefined,
|
|
69
|
+
stopLoss: params.stopLoss,
|
|
70
|
+
takeProfit: params.takeProfit,
|
|
71
|
+
comment: params.comment,
|
|
72
|
+
label: params.label
|
|
73
|
+
});
|
|
74
|
+
const response = await this.client.send(types_1.ProtoOAPayloadType.PROTO_OA_NEW_ORDER_REQ, encodeNewOrderReq(request));
|
|
75
|
+
return types_1.ProtoOAExecutionEvent.decode(response.payload ?? new Uint8Array());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.SpotwareTrading = SpotwareTrading;
|
|
79
|
+
// ts-proto skips a required field on the wire whenever its value equals the field's implicit
|
|
80
|
+
// proto2 default — for an enum with no explicit `[default = ...]` annotation, that's always its
|
|
81
|
+
// first declared member. orderType and tradeSide have no such annotation, so MARKET (1) and BUY
|
|
82
|
+
// (1) — the two most common choices — get silently dropped, and cTrader's server rejects the
|
|
83
|
+
// message as missing them. Confirmed this isn't fixable via ts-proto's codegen flags
|
|
84
|
+
// (disableProto2DefaultValues only affects fields with an explicit default). Field order doesn't
|
|
85
|
+
// matter on the wire, so appending the dropped ones after the normal encode is a safe, minimal
|
|
86
|
+
// fix that doesn't require hand-editing generated code.
|
|
87
|
+
function encodeNewOrderReq(request) {
|
|
88
|
+
const writer = types_1.ProtoOANewOrderReq.encode(request);
|
|
89
|
+
if (request.orderType === types_1.ProtoOAOrderType.MARKET) {
|
|
90
|
+
writer.uint32(32).int32(request.orderType);
|
|
91
|
+
}
|
|
92
|
+
if (request.tradeSide === types_1.ProtoOATradeSide.BUY) {
|
|
93
|
+
writer.uint32(40).int32(request.tradeSide);
|
|
94
|
+
}
|
|
95
|
+
return writer.finish();
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=spotware-trading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-trading.js","sourceRoot":"","sources":["../../src/trading/spotware-trading.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-codec.d.ts","sourceRoot":"","sources":["../../src/transport/frame-codec.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAKvD;AAED,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAA2B;IAEzC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAsBhC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FrameDecoder = void 0;
|
|
4
|
+
exports.encodeFrame = encodeFrame;
|
|
5
|
+
const LENGTH_PREFIX_BYTES = 4;
|
|
6
|
+
// cTrader closes the socket well before a legitimate message gets anywhere near this size;
|
|
7
|
+
// a length past it means the stream desynced (e.g. a framing bug), not a big message.
|
|
8
|
+
const MAX_FRAME_BYTES = 16 * 1024 * 1024;
|
|
9
|
+
function encodeFrame(payload) {
|
|
10
|
+
const length = Buffer.alloc(LENGTH_PREFIX_BYTES);
|
|
11
|
+
length.writeUInt32BE(payload.byteLength);
|
|
12
|
+
return Buffer.concat([length, payload]);
|
|
13
|
+
}
|
|
14
|
+
class FrameDecoder {
|
|
15
|
+
buffer = Buffer.alloc(0);
|
|
16
|
+
push(chunk) {
|
|
17
|
+
this.buffer = this.buffer.byteLength === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
|
|
18
|
+
const frames = [];
|
|
19
|
+
while (this.buffer.byteLength >= LENGTH_PREFIX_BYTES) {
|
|
20
|
+
const frameLength = this.buffer.readUInt32BE(0);
|
|
21
|
+
if (frameLength > MAX_FRAME_BYTES) {
|
|
22
|
+
throw new Error(`Frame length ${frameLength} exceeds the ${MAX_FRAME_BYTES} byte limit; stream is desynced`);
|
|
23
|
+
}
|
|
24
|
+
const frameEnd = LENGTH_PREFIX_BYTES + frameLength;
|
|
25
|
+
if (this.buffer.byteLength < frameEnd) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
frames.push(this.buffer.subarray(LENGTH_PREFIX_BYTES, frameEnd));
|
|
29
|
+
this.buffer = this.buffer.subarray(frameEnd);
|
|
30
|
+
}
|
|
31
|
+
return frames;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.FrameDecoder = FrameDecoder;
|
|
35
|
+
//# sourceMappingURL=frame-codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-codec.js","sourceRoot":"","sources":["../../src/transport/frame-codec.ts"],"names":[],"mappings":";;;AAMA,kCAKC;AAXD,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,2FAA2F;AAC3F,sFAAsF;AACtF,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzC,SAAgB,WAAW,CAAC,OAAmB;IAC3C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACjD,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,MAAa,YAAY;IACb,MAAM,GAAW,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEzC,IAAI,CAAC,KAAa;QACd,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;QAEzF,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,mBAAmB,EAAE,CAAC;YACnD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAChD,IAAI,WAAW,GAAG,eAAe,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CAAC,gBAAgB,WAAW,gBAAgB,eAAe,iCAAiC,CAAC,CAAC;YACjH,CAAC;YAED,MAAM,QAAQ,GAAG,mBAAmB,GAAG,WAAW,CAAC;YACnD,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC;gBACpC,MAAM;YACV,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC,CAAC;YACjE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACjD,CAAC;QAED,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAzBD,oCAyBC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./reconnect-backoff"), exports);
|
|
18
|
+
__exportStar(require("./spotware-host.enum"), exports);
|
|
19
|
+
__exportStar(require("./spotware-transport"), exports);
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,uDAAqC;AACrC,uDAAqC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class RateLimiter {
|
|
2
|
+
private readonly intervalMs;
|
|
3
|
+
private readonly queue;
|
|
4
|
+
private lastRunAt;
|
|
5
|
+
private timer;
|
|
6
|
+
constructor(intervalMs: number);
|
|
7
|
+
schedule(): Promise<void>;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
private ensureTimerRunning;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/transport/rate-limiter.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAW;IAKR,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,KAAK,CAA6B;gBAEb,UAAU,EAAE,MAAM;IAE/C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAezB,OAAO,IAAI,IAAI;IAQf,OAAO,CAAC,kBAAkB;CAiB7B"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimiter = void 0;
|
|
4
|
+
class RateLimiter {
|
|
5
|
+
intervalMs;
|
|
6
|
+
queue = [];
|
|
7
|
+
lastRunAt = 0;
|
|
8
|
+
timer;
|
|
9
|
+
constructor(intervalMs) {
|
|
10
|
+
this.intervalMs = intervalMs;
|
|
11
|
+
}
|
|
12
|
+
schedule() {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const elapsedSinceLastRun = Date.now() - this.lastRunAt;
|
|
15
|
+
if (!this.timer && elapsedSinceLastRun >= this.intervalMs) {
|
|
16
|
+
this.lastRunAt = Date.now();
|
|
17
|
+
resolve();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
this.queue.push(resolve);
|
|
21
|
+
this.ensureTimerRunning();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
dispose() {
|
|
25
|
+
if (this.timer) {
|
|
26
|
+
clearInterval(this.timer);
|
|
27
|
+
this.timer = undefined;
|
|
28
|
+
}
|
|
29
|
+
this.queue.length = 0;
|
|
30
|
+
}
|
|
31
|
+
ensureTimerRunning() {
|
|
32
|
+
if (this.timer) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.timer = setInterval(() => {
|
|
36
|
+
const next = this.queue.shift();
|
|
37
|
+
if (!next) {
|
|
38
|
+
clearInterval(this.timer);
|
|
39
|
+
this.timer = undefined;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.lastRunAt = Date.now();
|
|
43
|
+
next();
|
|
44
|
+
}, this.intervalMs);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.RateLimiter = RateLimiter;
|
|
48
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/transport/rate-limiter.ts"],"names":[],"mappings":";;;AAAA,MAAa,WAAW;IAKS;IAJZ,KAAK,GAAsB,EAAE,CAAC;IACvC,SAAS,GAAG,CAAC,CAAC;IACd,KAAK,CAA6B;IAE1C,YAA6B,UAAkB;QAAlB,eAAU,GAAV,UAAU,CAAQ;IAAG,CAAC;IAEnD,QAAQ;QACJ,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3B,MAAM,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;YAExD,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,mBAAmB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACxD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC5B,OAAO,EAAE,CAAC;gBACV,OAAO;YACX,CAAC;YAED,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC9B,CAAC,CAAC,CAAC;IACP,CAAC;IAED,OAAO;QACH,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1B,CAAC;IAEO,kBAAkB;QACtB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO;QACX,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACR,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC1B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;gBACvB,OAAO;YACX,CAAC;YAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5B,IAAI,EAAE,CAAC;QACX,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACxB,CAAC;CACJ;AA/CD,kCA+CC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface IReconnectBackoffOptions {
|
|
2
|
+
baseDelayMs: number;
|
|
3
|
+
maxDelayMs: number;
|
|
4
|
+
factor: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULT_RECONNECT_BACKOFF_OPTIONS: IReconnectBackoffOptions;
|
|
7
|
+
/**
|
|
8
|
+
* Equal jitter: half the capped exponential delay is fixed, half is randomized,
|
|
9
|
+
* so retries stay spread out under load instead of synchronizing into bursts.
|
|
10
|
+
*/
|
|
11
|
+
export declare function calculateReconnectDelayMs(attempt: number, options?: IReconnectBackoffOptions): number;
|
|
12
|
+
//# sourceMappingURL=reconnect-backoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconnect-backoff.d.ts","sourceRoot":"","sources":["../../src/transport/reconnect-backoff.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,iCAAiC,EAAE,wBAI/C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA4D,GAAG,MAAM,CAMxI"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_RECONNECT_BACKOFF_OPTIONS = void 0;
|
|
4
|
+
exports.calculateReconnectDelayMs = calculateReconnectDelayMs;
|
|
5
|
+
exports.DEFAULT_RECONNECT_BACKOFF_OPTIONS = {
|
|
6
|
+
baseDelayMs: 500,
|
|
7
|
+
maxDelayMs: 30_000,
|
|
8
|
+
factor: 2
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Equal jitter: half the capped exponential delay is fixed, half is randomized,
|
|
12
|
+
* so retries stay spread out under load instead of synchronizing into bursts.
|
|
13
|
+
*/
|
|
14
|
+
function calculateReconnectDelayMs(attempt, options = exports.DEFAULT_RECONNECT_BACKOFF_OPTIONS) {
|
|
15
|
+
const exponentialDelay = options.baseDelayMs * options.factor ** attempt;
|
|
16
|
+
const cappedDelay = Math.min(exponentialDelay, options.maxDelayMs);
|
|
17
|
+
const guaranteedDelay = cappedDelay / 2;
|
|
18
|
+
return Math.round(guaranteedDelay + Math.random() * guaranteedDelay);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=reconnect-backoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconnect-backoff.js","sourceRoot":"","sources":["../../src/transport/reconnect-backoff.ts"],"names":[],"mappings":";;;AAgBA,8DAMC;AAhBY,QAAA,iCAAiC,GAA6B;IACvE,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,MAAM;IAClB,MAAM,EAAE,CAAC;CACZ,CAAC;AAEF;;;GAGG;AACH,SAAgB,yBAAyB,CAAC,OAAe,EAAE,UAAoC,yCAAiC;IAC5H,MAAM,gBAAgB,GAAG,OAAO,CAAC,WAAW,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC;IACzE,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACnE,MAAM,eAAe,GAAG,WAAW,GAAG,CAAC,CAAC;IAExC,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,eAAe,CAAC,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-host.enum.d.ts","sourceRoot":"","sources":["../../src/transport/spotware-host.enum.ts"],"names":[],"mappings":"AAAA,oBAAY,YAAY;IACpB,IAAI,wBAAwB;IAC5B,IAAI,wBAAwB;CAC/B;AAED,eAAO,MAAM,aAAa,OAAO,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SPOTWARE_PORT = exports.SpotwareHost = void 0;
|
|
4
|
+
var SpotwareHost;
|
|
5
|
+
(function (SpotwareHost) {
|
|
6
|
+
SpotwareHost["DEMO"] = "demo.ctraderapi.com";
|
|
7
|
+
SpotwareHost["LIVE"] = "live.ctraderapi.com";
|
|
8
|
+
})(SpotwareHost || (exports.SpotwareHost = SpotwareHost = {}));
|
|
9
|
+
exports.SPOTWARE_PORT = 5035;
|
|
10
|
+
//# sourceMappingURL=spotware-host.enum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-host.enum.js","sourceRoot":"","sources":["../../src/transport/spotware-host.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,YAGX;AAHD,WAAY,YAAY;IACpB,4CAA4B,CAAA;IAC5B,4CAA4B,CAAA;AAChC,CAAC,EAHW,YAAY,4BAAZ,YAAY,QAGvB;AAEY,QAAA,aAAa,GAAG,IAAI,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ProtoOAPayloadType } from '../types';
|
|
2
|
+
export declare class SpotwareRateLimiter {
|
|
3
|
+
private readonly fivePerSecond;
|
|
4
|
+
private readonly fiftyPerSecond;
|
|
5
|
+
throttle(payloadType: ProtoOAPayloadType): Promise<void>;
|
|
6
|
+
dispose(): void;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=spotware-rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/transport/spotware-rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAe9C,qBAAa,mBAAmB;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgD;IAC9E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiD;IAE1E,QAAQ,CAAC,WAAW,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9D,OAAO,IAAI,IAAI;CAIlB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotwareRateLimiter = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const rate_limiter_1 = require("./rate-limiter");
|
|
6
|
+
// Spotware limits these to 5/sec and everything else to 50/sec.
|
|
7
|
+
// Intervals are set just above the theoretical minimum to stay clear of the limit.
|
|
8
|
+
// https://connect.spotware.com/docs/frequently-asked-questions
|
|
9
|
+
const FIVE_PER_SECOND_INTERVAL_MS = 210;
|
|
10
|
+
const FIFTY_PER_SECOND_INTERVAL_MS = 21;
|
|
11
|
+
const FIVE_PER_SECOND_PAYLOAD_TYPES = new Set([
|
|
12
|
+
types_1.ProtoOAPayloadType.PROTO_OA_GET_TRENDBARS_REQ,
|
|
13
|
+
types_1.ProtoOAPayloadType.PROTO_OA_GET_TICKDATA_REQ,
|
|
14
|
+
types_1.ProtoOAPayloadType.PROTO_OA_DEAL_LIST_REQ
|
|
15
|
+
]);
|
|
16
|
+
class SpotwareRateLimiter {
|
|
17
|
+
fivePerSecond = new rate_limiter_1.RateLimiter(FIVE_PER_SECOND_INTERVAL_MS);
|
|
18
|
+
fiftyPerSecond = new rate_limiter_1.RateLimiter(FIFTY_PER_SECOND_INTERVAL_MS);
|
|
19
|
+
async throttle(payloadType) {
|
|
20
|
+
const limiter = FIVE_PER_SECOND_PAYLOAD_TYPES.has(payloadType) ? this.fivePerSecond : this.fiftyPerSecond;
|
|
21
|
+
await limiter.schedule();
|
|
22
|
+
}
|
|
23
|
+
dispose() {
|
|
24
|
+
this.fivePerSecond.dispose();
|
|
25
|
+
this.fiftyPerSecond.dispose();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.SpotwareRateLimiter = SpotwareRateLimiter;
|
|
29
|
+
//# sourceMappingURL=spotware-rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-rate-limiter.js","sourceRoot":"","sources":["../../src/transport/spotware-rate-limiter.ts"],"names":[],"mappings":";;;AAAA,oCAA8C;AAC9C,iDAA6C;AAE7C,gEAAgE;AAChE,mFAAmF;AACnF,+DAA+D;AAC/D,MAAM,2BAA2B,GAAG,GAAG,CAAC;AACxC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAExC,MAAM,6BAA6B,GAAG,IAAI,GAAG,CAAqB;IAC9D,0BAAkB,CAAC,0BAA0B;IAC7C,0BAAkB,CAAC,yBAAyB;IAC5C,0BAAkB,CAAC,sBAAsB;CAC5C,CAAC,CAAC;AAEH,MAAa,mBAAmB;IACX,aAAa,GAAG,IAAI,0BAAW,CAAC,2BAA2B,CAAC,CAAC;IAC7D,cAAc,GAAG,IAAI,0BAAW,CAAC,4BAA4B,CAAC,CAAC;IAEhF,KAAK,CAAC,QAAQ,CAAC,WAA+B;QAC1C,MAAM,OAAO,GAAG,6BAA6B,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;QAC1G,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO;QACH,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;IAClC,CAAC;CACJ;AAbD,kDAaC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Duplex } from 'node:stream';
|
|
2
|
+
import { TypedEventEmitter, type EventMap } from '../shared/typed-event-emitter';
|
|
3
|
+
import { ProtoMessage } from '../types';
|
|
4
|
+
import { type IReconnectBackoffOptions } from './reconnect-backoff';
|
|
5
|
+
import { SpotwareHost } from './spotware-host.enum';
|
|
6
|
+
export type SpotwareDisconnectReason = 'manual' | 'dropped';
|
|
7
|
+
/**
|
|
8
|
+
* Resolves once the connection is fully ready to use (e.g. after the TLS handshake).
|
|
9
|
+
* Lets tests substitute a plain local TCP server without touching any transport logic.
|
|
10
|
+
*/
|
|
11
|
+
export type SpotwareSocketFactory = (port: number, host: string) => Promise<Duplex>;
|
|
12
|
+
export interface ISpotwareTransportEvents extends EventMap {
|
|
13
|
+
connected: [];
|
|
14
|
+
disconnected: [reason: SpotwareDisconnectReason];
|
|
15
|
+
reconnecting: [attempt: number, delayMs: number];
|
|
16
|
+
message: [message: ProtoMessage];
|
|
17
|
+
error: [error: Error];
|
|
18
|
+
}
|
|
19
|
+
export interface ISpotwareTransportOptions {
|
|
20
|
+
host: SpotwareHost;
|
|
21
|
+
port?: number;
|
|
22
|
+
reconnectBackoff?: IReconnectBackoffOptions;
|
|
23
|
+
socketFactory?: SpotwareSocketFactory;
|
|
24
|
+
staleConnectionTimeoutMs?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Opens the TCP/TLS connection to a cTrader host, frames Protobuf messages on the wire,
|
|
28
|
+
* and auto-reconnects with backoff on any drop that wasn't requested via disconnect().
|
|
29
|
+
* Knows nothing about auth or trading: messages are passed through as the raw ProtoMessage
|
|
30
|
+
* envelope, undecoded, for the client module to interpret.
|
|
31
|
+
*/
|
|
32
|
+
export declare class SpotwareTransport extends TypedEventEmitter<ISpotwareTransportEvents> {
|
|
33
|
+
private readonly host;
|
|
34
|
+
private readonly port;
|
|
35
|
+
private readonly reconnectBackoffOptions;
|
|
36
|
+
private readonly socketFactory;
|
|
37
|
+
private readonly staleConnectionTimeoutMs;
|
|
38
|
+
private readonly livenessCheckIntervalMs;
|
|
39
|
+
private readonly rateLimiter;
|
|
40
|
+
private socket;
|
|
41
|
+
private heartbeatTimer;
|
|
42
|
+
private livenessTimer;
|
|
43
|
+
private reconnectTimer;
|
|
44
|
+
private reconnectAttempt;
|
|
45
|
+
private disconnectRequested;
|
|
46
|
+
private lastMessageReceivedAt;
|
|
47
|
+
private hasConnectedOnce;
|
|
48
|
+
constructor(options: ISpotwareTransportOptions);
|
|
49
|
+
connect(): Promise<void>;
|
|
50
|
+
disconnect(): Promise<void>;
|
|
51
|
+
send(message: ProtoMessage): Promise<void>;
|
|
52
|
+
private attemptConnection;
|
|
53
|
+
private handleData;
|
|
54
|
+
private handleClose;
|
|
55
|
+
private scheduleReconnect;
|
|
56
|
+
private clearReconnectTimer;
|
|
57
|
+
private startHeartbeat;
|
|
58
|
+
private startLivenessWatchdog;
|
|
59
|
+
private stopLivenessWatchdog;
|
|
60
|
+
private stopHeartbeat;
|
|
61
|
+
private buildHeartbeatMessage;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=spotware-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-transport.d.ts","sourceRoot":"","sources":["../../src/transport/spotware-transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,EAAuB,YAAY,EAAoB,MAAM,UAAU,CAAC;AAE/E,OAAO,EAAgE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAElI,OAAO,EAAiB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAanE,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5D;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAqBpF,MAAM,WAAW,wBAAyB,SAAQ,QAAQ;IACtD,SAAS,EAAE,EAAE,CAAC;IACd,YAAY,EAAE,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IACjD,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACjD,OAAO,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACjC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACtC,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,wBAAwB,CAAC;IAC5C,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,iBAAiB,CAAC,wBAAwB,CAAC;IAC9E,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA2B;IACnE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwB;IACtD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAS;IAClD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,qBAAqB,CAAK;IAKlC,OAAO,CAAC,gBAAgB,CAAS;gBAErB,OAAO,EAAE,yBAAyB;IAU9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAelC,iBAAiB;IA+B/B,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,qBAAqB;CAMhC"}
|