Open dawadam opened 1 year ago
please see jspf-md-demo project
you need to install a custom msg factory
class MySessionContainer extends SessionContainer {
protected makeSessionFactory (description: ISessionDescription): ISessionMsgFactory {
return new MsgFact(description)
}
}
you will need to add new property here which constructs a header object based on json properties
public header (msgType: string, seqNum: number, time: Date, overrideData?: Partial<IStandardHeader>): ILooseObject {
const description = this.description
const bodyLength: number = Math.max(4, description.BodyLengthChars ?? 7)
const placeHolder = Math.pow(10, bodyLength - 1) + 1
const o: IStandardHeader = {
BeginString: description.BeginString,
BodyLength: placeHolder,
MsgType: msgType,
SenderCompID: description.SenderCompId,
// e.g. SenderSubID: 'QUOTE',
MsgSeqNum: seqNum,
SendingTime: time,
TargetCompID: description.TargetCompID,
TargetSubID: description.TargetSubID,
...overrideData
}
return this.mutate(o, 'StandardHeader')
}
it is a bug in sense the base class should send this field but its missing and this is only way to override at the moment - i will patch at some point soon such that the default implementtion will take care of it
Ok, but ILogon don't include SenderSubID property.
export interface ILogon {
StandardHeader: IStandardHeader
EncryptMethod: number// 98
HeartBtInt: number// 108
RawDataLength?: number// 95
RawData?: Buffer// 96
ResetSeqNumFlag?: boolean// 141
NextExpectedMsgSeqNum?: number// 789
MaxMessageSize?: number// 383
MsgTypeGrp?: IMsgTypeGrp[]
TestMessageIndicator?: boolean// 464
Username?: string// 553
Password?: string// 554
NewPassword?: string// 925
EncryptedPasswordMethod?: number// 1400
EncryptedPasswordLen?: number// 1401
EncryptedPassword?: Buffer// 1402
EncryptedNewPasswordLen?: number// 1403
EncryptedNewPassword?: Buffer// 1404
SessionStatus?: number// 1409
DefaultApplVerID: string// 1137
DefaultApplExtID?: number// 1407
DefaultCstmApplVerID?: string// 1408
Text?: string// 58
EncodedTextLen?: number// 354
EncodedText?: Buffer// 355
StandardTrailer: IStandardTrailer
}
It doesn't seem to work, here is the complete code.
_jspurefixmsg-fact.ts
import { ISessionDescription, MsgType, ASessionMsgFactory } from 'jspurefix'
import { ILogout } from 'jspurefix/dist/types/FIX4.4/cserver/logout'
import { ILooseObject } from 'jspurefix/dist/collections/collection'
import { EncryptMethod, ILogon, IStandardHeader } from 'jspurefix/dist/types/FIX4.4/cserver'
/**
*
*/
export class MsgFact extends ASessionMsgFactory {
constructor(readonly description: ISessionDescription) {
super(description, (_description: ISessionDescription, _type: string, o: ILooseObject) => o)
this.isAscii = description?.application?.protocol === 'ascii'
}
/**
*
*/
public logon(): ILooseObject {
const description = this.description
const o: ILogon = {
Username: description.Username,
Password: description.Password,
HeartBtInt: description.HeartBtInt,
ResetSeqNumFlag: description.ResetSeqNumFlag,
// @ts-ignore
SenderSubID: description.SenderSubID,
EncryptMethod: EncryptMethod.NoneOther,
StandardHeader: undefined,
StandardTrailer: undefined
}
return this.mutate(o, MsgType.Logon)
}
/**
*
*/
public logout(text: string): ILooseObject {
const o: ILogout = {
Text: text,
StandardHeader: undefined,
StandardTrailer: undefined
}
return this.mutate(o, MsgType.Logout)
}
/**
*
*/
public header(msgType: string, seqNum: number, time: Date, overrideData?: Partial<IStandardHeader>): ILooseObject {
const description = this.description
const bodyLength: number = Math.max(4, description.BodyLengthChars ?? 7)
const placeHolder = Math.pow(10, bodyLength - 1) + 1
const o: IStandardHeader = {
BeginString: description.BeginString,
BodyLength: placeHolder,
MsgType: msgType,
SenderCompID: description.SenderCompId,
MsgSeqNum: seqNum,
SendingTime: time,
TargetCompID: description.TargetCompID,
TargetSubID: description.TargetSubID,
...overrideData
}
return this.mutate(o, 'StandardHeader')
}
}
jspurefix.ts
import "reflect-metadata"
import {
EngineFactory,
SessionLauncher,
AsciiSession,
MsgView,
IJsFixLogger,
IJsFixConfig,
SessionContainer,
ISessionDescription,
ISessionMsgFactory
} from 'jspurefix'
import { MsgFact } from './jspurefix_msg-fact'
/**
*
*/
class FixTest extends AsciiSession {
private readonly logger: IJsFixLogger
private readonly fixLog: IJsFixLogger
constructor(public readonly config: IJsFixConfig) {
super(config)
this.logReceivedMsgs = true
this.fixLog = config.logFactory.plain(`jsfix.${config.description.application.name}.txt`)
this.logger = config.logFactory.logger(`${this.me}:TradeCaptureClient`)
}
/**
*
* @param msgType
* @param txt
*/
protected onDecoded(msgType: string, txt: string): void {
console.log('onDecoded', msgType, txt)
this.fixLog.info(txt)
}
/**
*
*/
protected onEncoded(msgType: string, txt: string): void {
console.log('onEncoded', msgType, txt)
this.logger.info('test')
this.fixLog.info(txt)
}
/**
*
* @param msgType
* @param view
*/
protected onApplicationMsg(msgType: string, view: MsgView): void {
console.log('onApplicationMsg', msgType)
switch (msgType) {
}
}
/**
*
* @param view
*/
protected onReady(view: MsgView): void {
console.log('READY')
}
/**
*
* @param error
*/
protected onStopped(error?: Error): void {
console.log('STOPPPED')
}
/**
*
*/
protected onLogon(view: MsgView, user: string, password: string): boolean {
console.log('LOGON')
return true
}
}
/**
*
*/
class MySessionContainer extends SessionContainer {
protected makeSessionFactory(description: ISessionDescription): ISessionMsgFactory {
return new MsgFact(description)
}
}
/**
*
*/
class AppLauncher extends SessionLauncher {
public constructor(client: string = '../../jspurefix-test-initiator.json') {
super(client)
this.sessionContainer = new MySessionContainer()
this.root = __dirname
}
protected override makeFactory(config: IJsFixConfig): EngineFactory {
return {
makeSession: () => new FixTest(config)
} as EngineFactory
}
}
const l = new AppLauncher()
l.exec()
jspurefix-test-initiator.json
{
"application": {
"reconnectSeconds": 10,
"type": "initiator",
"name": "test_ctrader",
"tcp": {
"host": "h51.p.ctrader.com",
"port": 5201
},
"protocol": "ascii",
"dictionary": "../../resources/FIX44-CSERVER.xml"
},
"BeginString": "FIX4.4",
"Username": "123",
"Password": "abc",
"EncryptMethod": 0,
"ResetSeqNumFlag": true,
"HeartBtInt": 30,
"SenderCompId": "demo.icmarkets.123",
"TargetCompID": "cServer",
"SenderSubID": "QUOTE"
}
I think i will be wait the patch. Code is big for just connection. Do you have free time for that right now? :)
the field does not go in the logon message - it should be in the header
public header(msgType: string, seqNum: number, time: Date, overrideData?: Partial<IStandardHeader>): ILooseObject {
const description = this.description
const bodyLength: number = Math.max(4, description.BodyLengthChars ?? 7)
const placeHolder = Math.pow(10, bodyLength - 1) + 1
const o: IStandardHeader = {
BeginString: description.BeginString,
BodyLength: placeHolder,
MsgType: msgType,
SenderCompID: description.SenderCompId,
MsgSeqNum: seqNum,
SendingTime: time,
TargetCompID: description.TargetCompID,
TargetSubID: description.TargetSubID,
...overrideData
}
return this.mutate(o, 'StandardHeader')
}
Thanks, it works, the FIX message is correct.
8=FIX4.4|9=0000122|35=A|49=demo.icmarkets.123|56=cServer|34=1|50=QUOTE|52=20230827-20:51:43.527|98=0|108=30|141=Y|553=8665823|554=abc|10=000|
No response from the server, but that must be another issue.
I am trying to login to cTrader broker and "SenderSubID" parametter is needed. I'm using a ctrader custom dictionary and adding this parameter on the json file, but it's not being sent. And no response from the server.
json file :
Application code :
Logon fix message :
8=FIX4.4|9=0000105|35=A|49=demo.aaa.123456|56=cServer|34=1|52=20230827-16:38:22.081|98=0|108=30|141=Y|553=123456|554=azerty|10=176|
No SenderSubID parameter, identifier is 50
How add it in login message ?