Skip to content

Commit 4a4d03d

Browse files
authored
Merge pull request #6 from KendelChopp/accept-v1-subscriptions
Accept v1 subscriptions
2 parents 24074fc + 769e9d0 commit 4a4d03d

7 files changed

Lines changed: 46 additions & 28 deletions

File tree

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const backtest = new Backtest({
3636
});
3737
```
3838

39-
3. Run your algorithm replacing Alpaca/websockets accordingly.
39+
3. Run your algorithm replacing Alpaca/websockets accordingly.
4040

4141
For example (commented out lines are the old lines to be replaced):
4242
```JavaScript
@@ -61,6 +61,10 @@ This table lists the version of @kendelchopp/alpaca-js-backtesting with the vers
6161
| @kendelchopp/alpaca-js-backtesting | @alpacahq/alpaca-trade-api-js |
6262
| ---------------------------------- | ----------------------------- |
6363
| 1.0.0 | 1.4.1 |
64+
| 1.1.0 | 1.4.1 |
65+
| 1.1.1 | 1.4.1 |
66+
| 1.1.2 | 1.4.1 |
67+
| 1.1.3 | 1.4.1 |
6468

6569
## Relevant Functions
6670
### Backtest#createOrder
@@ -99,7 +103,7 @@ Runs a function when establishing the connection. When backtesting, you will usu
99103
#### Example
100104
```JavaScript
101105
client.onConnect(() => {
102-
client.subscribe(['AM.SPY']);
106+
client.subscribe(['alpacadatav1/AM.SPY']);
103107
});
104108
```
105109

@@ -114,22 +118,20 @@ client.onDisconnect(() => {
114118
```
115119

116120
### Websocket#onStockAggMin
117-
This runs your function when a simulated minute occurrs and returns the relevant data. This is where your trading will likely take place
121+
This runs your function when a simulated minute occurrs and returns the relevant data. This is where your trading will likely take place. Subject will look something like `AM.SPY` and data will look like a standard bar data.
118122

119123
#### Example
120124
```JavaScript
121125
client.onStockAggMin((subject, data) => {
122-
const priceArray = JSON.parse(data);
123-
124-
if (priceArray[0].closePrice < 500) {
126+
if (data.closePrice < 500) {
125127
backtest.createOrder({
126128
symbol: 'SPY',
127129
qty: 300,
128130
side: 'buy',
129131
type: 'market',
130132
time_in_force: 'day'
131133
});
132-
134+
133135
console.log('Bought SPY');
134136
}
135137
});
@@ -141,6 +143,6 @@ Subscribes to the channels listed. Currently, only aggregate minute channels are
141143
#### Example
142144
```JavaScript
143145
client.onConnect(() => {
144-
client.subscribe(['AM.SPY', 'AM.AAPL']);
146+
client.subscribe(['alpacadatav1/AM.SPY', 'alpacadatav1/AM.AAPL']);
145147
});
146148
```

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kendelchopp/alpaca-js-backtesting",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"description": "",
55
"main": "src/index.js",
66
"scripts": {

src/MarketData/MarketData.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,24 @@ class MarketData {
3131
* Simulates a minute by mapping the security's information for that minute and updating the
3232
* current price of all the securities
3333
*
34-
* @returns {string} the stringified map of information to simulate coming over a request
34+
* @returns {Object[]} object containing subjects and data
3535
*/
3636
simulateMinute() {
3737
const validSecurities = _.filter(this.securities, (security) => Boolean(security.data[this.time]));
3838
const dataMap = _.map(validSecurities, (security) => {
3939
security.price = security.data[this.time].closePrice;
4040
return {
41-
...security.data[this.time],
42-
ev: 'AM',
43-
sym: security.symbol
44-
}
41+
subject: `AM.${security.symbol}`,
42+
data: {
43+
...security.data[this.time],
44+
ev: 'AM',
45+
symbol: security.symbol
46+
}
47+
};
4548
});
4649

4750
this.time++;
48-
return JSON.stringify(dataMap);
51+
return dataMap;
4952
}
5053

5154
/**

src/MarketData/MarketData.test.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,16 @@ describe('MarketData', () => {
7979
expect(marketData.time).toBe(1);
8080
});
8181

82-
test('returns a stringified map of the valid securities', () => {
82+
test('returns a map of the valid securities with subject and data', () => {
8383
const simulation = marketData.simulateMinute();
84-
const expected = JSON.stringify([{
85-
closePrice,
86-
ev: 'AM',
87-
sym: validSecurity.symbol
88-
}]);
84+
const expected = [{
85+
subject: `AM.${validSecurity.symbol}`,
86+
data: {
87+
closePrice,
88+
ev: 'AM',
89+
symbol: validSecurity.symbol
90+
}
91+
}];
8992

9093
expect(simulation).toEqual(expected);
9194
});

src/Websocket/Websocket.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ class Websocket {
6262
*/
6363
async loadData() {
6464
const rawChannels = _.map(this.channels, (channel) => {
65+
const isV1Minute = channel.startsWith('alpacadatav1/AM.');
6566
const isMinute = channel.startsWith('AM.');
66-
if (!isMinute) {
67+
if (!isMinute && !isV1Minute) {
6768
throw new Error('Only minute aggregates are supported at this time.');
6869
}
6970

70-
return channel.substring(3);
71+
return isMinute ? channel.substring(3) : channel.substring(16);
7172
});
7273

7374
const channelString = _.join(rawChannels, ',');
@@ -96,7 +97,10 @@ class Websocket {
9697
}
9798

9899
while (this._marketData.hasData) {
99-
this.stockAggMinCallback('AM', this._marketData.simulateMinute());
100+
const simulatedSecurities = this._marketData.simulateMinute();
101+
_.forEach(simulatedSecurities, (security) => {
102+
this.stockAggMinCallback(security.subject, security.data);
103+
});
100104
}
101105
}
102106

src/Websocket/Websocket.test.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('Websocket', () => {
5454
let barRequest, channels, channelString, rawChannels;
5555

5656
beforeEach(() => {
57-
channels = ['AM.SPY', 'AM.AAPL'];
57+
channels = ['AM.SPY', 'alpacadatav1/AM.AAPL'];
5858
rawChannels = ['SPY', 'AAPL'];
5959
channelString = 'SPY,AAPL';
6060
websocket.channels = channels;
@@ -130,14 +130,20 @@ describe('Websocket', () => {
130130
return minuteCount++ < 1;
131131
});
132132

133-
simulatedMinute = { some: 'minute' };
133+
simulatedMinute = [{
134+
subject: 'AM.AAPL',
135+
data: {
136+
some: 'data'
137+
}
138+
}];
139+
134140
jest.spyOn(marketData, 'simulateMinute').mockReturnValue(simulatedMinute);
135141
});
136142

137143
test('calls the callback the correct number of times', () => {
138144
websocket.runSimulation();
139145
expect(marketData.simulateMinute).toBeCalled();
140-
expect(callback).toBeCalledWith('AM', simulatedMinute);
146+
expect(callback).toBeCalledWith(simulatedMinute[0].subject, simulatedMinute[0].data);
141147
});
142148
});
143149

0 commit comments

Comments
 (0)