adding quickfix library

This commit is contained in:
Ramiro Paz
2026-03-12 12:08:34 -03:00
parent 9e55c5c562
commit c09a1fd21a
1311 changed files with 1887342 additions and 2 deletions

View File

@ -0,0 +1,118 @@
// Copyright (c) quickfixengine.org All rights reserved.
//
// This file may be distributed under the terms of the quickfixengine.org
// license as defined by quickfixengine.org and appearing in the file
// LICENSE included in the packaging of this file.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.
//
// See http://www.quickfixengine.org/LICENSE for licensing information.
//
// Contact ask@quickfixengine.org if any conditions of this licensing
// are not clear to you.
package file
import (
"fmt"
"log"
"os"
"path"
"github.com/quickfixgo/quickfix"
"github.com/quickfixgo/quickfix/config"
)
type fileLog struct {
eventLogger *log.Logger
messageLogger *log.Logger
}
func (l fileLog) OnIncoming(msg []byte) {
l.messageLogger.Print(string(msg))
}
func (l fileLog) OnOutgoing(msg []byte) {
l.messageLogger.Print(string(msg))
}
func (l fileLog) OnEvent(msg string) {
l.eventLogger.Print(msg)
}
func (l fileLog) OnEventf(format string, v ...interface{}) {
l.eventLogger.Printf(format, v...)
}
type fileLogFactory struct {
globalLogPath string
sessionLogPaths map[quickfix.SessionID]string
}
// NewLogFactory creates an instance of LogFactory that writes messages and events to file.
// The location of global and session log files is configured via FileLogPath.
func NewLogFactory(settings *quickfix.Settings) (quickfix.LogFactory, error) {
logFactory := fileLogFactory{}
var err error
if logFactory.globalLogPath, err = settings.GlobalSettings().Setting(config.FileLogPath); err != nil {
return logFactory, err
}
logFactory.sessionLogPaths = make(map[quickfix.SessionID]string)
for sid, sessionSettings := range settings.SessionSettings() {
logPath, err := sessionSettings.Setting(config.FileLogPath)
if err != nil {
return logFactory, err
}
logFactory.sessionLogPaths[sid] = logPath
}
return logFactory, nil
}
func newFileLog(prefix string, logPath string) (fileLog, error) {
l := fileLog{}
eventLogName := path.Join(logPath, prefix+".event.current.log")
messageLogName := path.Join(logPath, prefix+".messages.current.log")
if err := os.MkdirAll(logPath, os.ModePerm); err != nil {
return l, err
}
fileFlags := os.O_RDWR | os.O_CREATE | os.O_APPEND
eventFile, err := os.OpenFile(eventLogName, fileFlags, os.ModePerm)
if err != nil {
return l, err
}
messageFile, err := os.OpenFile(messageLogName, fileFlags, os.ModePerm)
if err != nil {
return l, err
}
logFlag := log.Ldate | log.Ltime | log.Lmicroseconds | log.LUTC
l.eventLogger = log.New(eventFile, "", logFlag)
l.messageLogger = log.New(messageFile, "", logFlag)
return l, nil
}
func (f fileLogFactory) Create() (quickfix.Log, error) {
return newFileLog("GLOBAL", f.globalLogPath)
}
func (f fileLogFactory) CreateSessionLog(sessionID quickfix.SessionID) (quickfix.Log, error) {
logPath, ok := f.sessionLogPaths[sessionID]
if !ok {
return nil, fmt.Errorf("logger not defined for %v", sessionID)
}
prefix := sessionIDFilenamePrefix(sessionID)
return newFileLog(prefix, logPath)
}

View File

@ -0,0 +1,147 @@
// Copyright (c) quickfixengine.org All rights reserved.
//
// This file may be distributed under the terms of the quickfixengine.org
// license as defined by quickfixengine.org and appearing in the file
// LICENSE included in the packaging of this file.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.
//
// See http://www.quickfixengine.org/LICENSE for licensing information.
//
// Contact ask@quickfixengine.org if any conditions of this licensing
// are not clear to you.
package file
import (
"bufio"
"fmt"
"os"
"path"
"strings"
"testing"
"github.com/quickfixgo/quickfix"
)
func TestFileLog_NewFileLogFactory(t *testing.T) {
_, err := NewLogFactory(quickfix.NewSettings())
if err == nil {
t.Error("Should expect error when settings have no file log path")
}
cfg := `
# default settings for sessions
[DEFAULT]
ConnectionType=initiator
ReconnectInterval=60
SenderCompID=TW
FileLogPath=.
# session definition
[SESSION]
BeginString=FIX.4.1
TargetCompID=ARCA
FileLogPath=mydir
[SESSION]
BeginString=FIX.4.1
TargetCompID=ARCA
SessionQualifier=BS
`
stringReader := strings.NewReader(cfg)
settings, _ := quickfix.ParseSettings(stringReader)
factory, err := NewLogFactory(settings)
if err != nil {
t.Error("Did not expect error", err)
}
if factory == nil {
t.Error("Should have returned factory")
}
}
type fileLogHelper struct {
LogPath string
Prefix string
Log quickfix.Log
}
func newFileLogHelper(t *testing.T) *fileLogHelper {
prefix := "myprefix"
logPath := path.Join(os.TempDir(), fmt.Sprintf("TestLogStore-%d", os.Getpid()))
log, err := newFileLog(prefix, logPath)
if err != nil {
t.Error("Unexpected error", err)
}
return &fileLogHelper{
LogPath: logPath,
Prefix: prefix,
Log: log,
}
}
func TestNewFileLog(t *testing.T) {
helper := newFileLogHelper(t)
tests := []struct {
expectedPath string
}{
{path.Join(helper.LogPath, fmt.Sprintf("%v.messages.current.log", helper.Prefix))},
{path.Join(helper.LogPath, fmt.Sprintf("%v.event.current.log", helper.Prefix))},
}
for _, test := range tests {
if _, err := os.Stat(test.expectedPath); os.IsNotExist(err) {
t.Errorf("%v does not exist", test.expectedPath)
}
}
}
func TestFileLog_Append(t *testing.T) {
helper := newFileLogHelper(t)
messageLogFile, err := os.Open(path.Join(helper.LogPath, fmt.Sprintf("%v.messages.current.log", helper.Prefix)))
if err != nil {
t.Error("Unexpected error", err)
}
defer messageLogFile.Close()
eventLogFile, err := os.Open(path.Join(helper.LogPath, fmt.Sprintf("%v.event.current.log", helper.Prefix)))
if err != nil {
t.Error("Unexpected error", err)
}
defer eventLogFile.Close()
messageScanner := bufio.NewScanner(messageLogFile)
eventScanner := bufio.NewScanner(eventLogFile)
helper.Log.OnIncoming([]byte("incoming"))
if !messageScanner.Scan() {
t.Error("Unexpected EOF")
}
helper.Log.OnEvent("Event")
if !eventScanner.Scan() {
t.Error("Unexpected EOF")
}
newHelper := newFileLogHelper(t)
newHelper.Log.OnIncoming([]byte("incoming"))
if !messageScanner.Scan() {
t.Error("Unexpected EOF")
}
newHelper.Log.OnEvent("Event")
if !eventScanner.Scan() {
t.Error("Unexpected EOF")
}
}

View File

@ -0,0 +1,58 @@
// Copyright (c) quickfixengine.org All rights reserved.
//
// This file may be distributed under the terms of the quickfixengine.org
// license as defined by quickfixengine.org and appearing in the file
// LICENSE included in the packaging of this file.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.
//
// See http://www.quickfixengine.org/LICENSE for licensing information.
//
// Contact ask@quickfixengine.org if any conditions of this licensing
// are not clear to you.
package file
import (
"fmt"
"os"
"strings"
"github.com/quickfixgo/quickfix"
)
func sessionIDFilenamePrefix(s quickfix.SessionID) string {
sender := []string{s.SenderCompID}
if s.SenderSubID != "" {
sender = append(sender, s.SenderSubID)
}
if s.SenderLocationID != "" {
sender = append(sender, s.SenderLocationID)
}
target := []string{s.TargetCompID}
if s.TargetSubID != "" {
target = append(target, s.TargetSubID)
}
if s.TargetLocationID != "" {
target = append(target, s.TargetLocationID)
}
fname := []string{s.BeginString, strings.Join(sender, "_"), strings.Join(target, "_")}
if s.Qualifier != "" {
fname = append(fname, s.Qualifier)
}
return strings.Join(fname, "-")
}
// openOrCreateFile opens a file for reading and writing, creating it if necessary.
func openOrCreateFile(fname string, perm os.FileMode) (f *os.File, err error) {
if f, err = os.OpenFile(fname, os.O_RDWR, perm); err != nil {
if f, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE, perm); err != nil {
return nil, fmt.Errorf("error opening or creating file: %s: %s", fname, err.Error())
}
}
return f, nil
}

View File

@ -0,0 +1,82 @@
// Copyright (c) quickfixengine.org All rights reserved.
//
// This file may be distributed under the terms of the quickfixengine.org
// license as defined by quickfixengine.org and appearing in the file
// LICENSE included in the packaging of this file.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.
//
// See http://www.quickfixengine.org/LICENSE for licensing information.
//
// Contact ask@quickfixengine.org if any conditions of this licensing
// are not clear to you.
package file
import (
"fmt"
"os"
"path"
"testing"
"github.com/quickfixgo/quickfix"
"github.com/stretchr/testify/require"
)
func requireNotFileExists(t *testing.T, fname string) {
_, err := os.Stat(fname)
require.NotNil(t, err)
require.True(t, os.IsNotExist(err))
}
func requireFileExists(t *testing.T, fname string) {
_, err := os.Stat(fname)
require.Nil(t, err)
}
func TestSessionIDFilename_MinimallyQualifiedSessionID(t *testing.T) {
// When the session ID is
sessionID := quickfix.SessionID{BeginString: "FIX.4.4", SenderCompID: "SENDER", TargetCompID: "TARGET"}
// Then the filename should be
require.Equal(t, "FIX.4.4-SENDER-TARGET", sessionIDFilenamePrefix(sessionID))
}
func TestSessionIDFilename_FullyQualifiedSessionID(t *testing.T) {
// When the session ID is
sessionID := quickfix.SessionID{
BeginString: "FIX.4.4",
SenderCompID: "A",
SenderSubID: "B",
SenderLocationID: "C",
TargetCompID: "D",
TargetSubID: "E",
TargetLocationID: "F",
Qualifier: "G",
}
// Then the filename should be
require.Equal(t, "FIX.4.4-A_B_C-D_E_F-G", sessionIDFilenamePrefix(sessionID))
}
func TestOpenOrCreateFile(t *testing.T) {
// When the file doesn't exist yet
fname := path.Join(os.TempDir(), fmt.Sprintf("TestOpenOrCreateFile-%d", os.Getpid()))
requireNotFileExists(t, fname)
defer os.Remove(fname)
// Then it should be created
f, err := openOrCreateFile(fname, 0664)
require.Nil(t, err)
requireFileExists(t, fname)
// When the file already exists
f.Close()
// Then it should be opened
f, err = openOrCreateFile(fname, 0664)
require.Nil(t, err)
require.Nil(t, f.Close())
}