package order import ( "errors" "fmt" "strings" ) // var error definitions var ( ErrInvalidTimeInForce = errors.New("invalid time in force value provided") ErrUnsupportedTimeInForce = errors.New("unsupported time in force value") ) // TimeInForce enforces a standard for time-in-force values across the code base. type TimeInForce uint16 // TimeInForce types const ( UnknownTIF TimeInForce = 0 GoodTillCancel TimeInForce = 1 << iota GoodTillDay GoodTillTime GoodTillCrossing FillOrKill ImmediateOrCancel PostOnly StopOrReduce supportedTimeInForceFlag = GoodTillCancel | GoodTillDay | GoodTillTime | GoodTillCrossing | FillOrKill | ImmediateOrCancel | PostOnly | StopOrReduce ) // time-in-force string representations const ( gtcStr = "GTC" gtdStr = "GTD" gttStr = "GTT" gtxStr = "GTX" fokStr = "FOK" iocStr = "IOC" postonlyStr = "POSTONLY" sorStr = "SOR" ) // Is checks to see if the enum contains the flag func (t TimeInForce) Is(in TimeInForce) bool { return in != 0 && t&in == in } // StringToTimeInForce converts time in force string value to TimeInForce instance. func StringToTimeInForce(timeInForce string) (TimeInForce, error) { var result TimeInForce timeInForce = strings.ToUpper(timeInForce) switch timeInForce { case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", iocStr: result = ImmediateOrCancel case "GOODTILLCANCEL", "GOODTILCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", gtcStr: result = GoodTillCancel case "GOODTILLDAY", "GOOD_TIL_DAY", "GOOD_TILL_DAY", gtdStr: result = GoodTillDay case "GOODTILLTIME", "GOOD_TIL_TIME", gttStr: result = GoodTillTime case "GOODTILLCROSSING", "GOOD_TIL_CROSSING", "GOOD TIL CROSSING", "GOOD_TILL_CROSSING", gtxStr: result = GoodTillCrossing case "FILLORKILL", "FILL_OR_KILL", fokStr: result = FillOrKill case "POC", "POST_ONLY", "PENDINGORCANCEL", postonlyStr: result = PostOnly case "STOPORREDUCE", "STOP_OR_REDUCE", sorStr: result = StopOrReduce } if result == UnknownTIF && timeInForce != "" { return UnknownTIF, fmt.Errorf("%w: tif=%s", ErrInvalidTimeInForce, timeInForce) } return result, nil } // IsValid returns whether or not the supplied time in force value is valid or // not func (t TimeInForce) IsValid() bool { // Neither ImmediateOrCancel nor FillOrKill can coexist with anything else // If either bit is set then it must be the only bit set isIOCorFOK := t&(ImmediateOrCancel|FillOrKill) != 0 hasTwoBitsSet := t&(t-1) != 0 if isIOCorFOK && hasTwoBitsSet { return false } return t == UnknownTIF || supportedTimeInForceFlag&t == t } // String implements the stringer interface. func (t TimeInForce) String() string { if t == UnknownTIF { return "" } var tifStrings []string if t.Is(ImmediateOrCancel) { tifStrings = append(tifStrings, iocStr) } if t.Is(GoodTillCancel) { tifStrings = append(tifStrings, gtcStr) } if t.Is(GoodTillDay) { tifStrings = append(tifStrings, gtdStr) } if t.Is(GoodTillTime) { tifStrings = append(tifStrings, gttStr) } if t.Is(GoodTillCrossing) { tifStrings = append(tifStrings, gtxStr) } if t.Is(FillOrKill) { tifStrings = append(tifStrings, fokStr) } if t.Is(PostOnly) { tifStrings = append(tifStrings, postonlyStr) } if t.Is(StopOrReduce) { tifStrings = append(tifStrings, sorStr) } if len(tifStrings) == 0 { return "UNKNOWN" } return strings.Join(tifStrings, ",") } // Lower returns a lower case string representation of time-in-force func (t TimeInForce) Lower() string { return strings.ToLower(t.String()) } // UnmarshalJSON deserializes a string data into TimeInForce instance. func (t *TimeInForce) UnmarshalJSON(data []byte) error { for val := range strings.SplitSeq(strings.Trim(string(data), `"`), ",") { tif, err := StringToTimeInForce(val) if err != nil { return err } *t |= tif } return nil } // MarshalJSON returns the JSON-encoded order time-in-force value func (t TimeInForce) MarshalJSON() ([]byte, error) { return []byte(`"` + t.String() + `"`), nil }