Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace C2 symbol for C2 Exchange symbol #8604

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
/// </summary>
public class Collective2SignalExport : BaseSignalExport
{
/// <summary>
/// Hashset of symbols whose market is unknown but have already been seen by
/// this signal export manager
/// </summary>
private HashSet<string> _unknownMarketSymbols;

/// <summary>
/// API key provided by Collective2
/// </summary>
Expand Down Expand Up @@ -86,6 +92,7 @@ public class Collective2SignalExport : BaseSignalExport
/// <param name="useWhiteLabelApi">Whether to use the white-label API instead of the general one</param>
public Collective2SignalExport(string apiKey, int systemId, bool useWhiteLabelApi = false)
{
_unknownMarketSymbols = new HashSet<string>();
_apiKey = apiKey;
_systemId = systemId;
Destination = new Uri(useWhiteLabelApi
Expand Down Expand Up @@ -131,7 +138,7 @@ protected bool ConvertHoldingsToCollective2(SignalExportTargetParameters paramet
{
_algorithm = parameters.Algorithm;
var targets = parameters.Targets;
positions = new List<Collective2Position>();
positions = [];
foreach (var target in targets)
{
if (target == null)
Expand All @@ -140,27 +147,29 @@ protected bool ConvertHoldingsToCollective2(SignalExportTargetParameters paramet
return false;
}

if (!ConvertTypeOfSymbol(target.Symbol, out string typeOfSymbol))
var securityType = GetSecurityTypeAcronym(target.Symbol.SecurityType);
if (securityType == null)
{
return false;
continue;
}

var symbol = _algorithm.Ticker(target.Symbol);
if (target.Symbol.SecurityType == SecurityType.Future)
var maturityMonthYear = GetMaturityMonthYear(target.Symbol);
if (maturityMonthYear?.Length == 0)
{
symbol = $"@{SymbolRepresentation.GenerateFutureTicker(target.Symbol.ID.Symbol, target.Symbol.ID.Date, doubleDigitsYear: false, includeExpirationDate: false)}";
}
else if (target.Symbol.SecurityType.IsOption())
{
symbol = SymbolRepresentation.GenerateOptionTicker(target.Symbol);
continue;
}

positions.Add(new Collective2Position
{
C2Symbol = new C2Symbol
ExchangeSymbol = new C2ExchangeSymbol
{
FullSymbol = symbol,
SymbolType = typeOfSymbol,
Symbol = GetSymbol(target.Symbol),
Currency = parameters.Algorithm.AccountCurrency,
SecurityExchange = GetMICExchangeCode(target.Symbol),
SecurityType = securityType,
MaturityMonthYear = maturityMonthYear,
PutOrCall = GetPutOrCallValue(target.Symbol),
StrikePrice = GetStrikePrice(target.Symbol)
},
Quantity = ConvertPercentageToQuantity(_algorithm, target),
});
Expand All @@ -169,46 +178,6 @@ protected bool ConvertHoldingsToCollective2(SignalExportTargetParameters paramet
return true;
}

/// <summary>
/// Classifies a symbol type into the possible symbol types values defined
/// by Collective2 API.
/// </summary>
/// <param name="targetSymbol">Symbol of the desired position</param>
/// <param name="typeOfSymbol">The type of the symbol according to Collective2 API</param>
/// <returns>True if the symbol's type is supported by Collective2, false otherwise</returns>
private bool ConvertTypeOfSymbol(Symbol targetSymbol, out string typeOfSymbol)
{
switch (targetSymbol.SecurityType)
{
case SecurityType.Equity:
typeOfSymbol = "stock";
break;
case SecurityType.Option:
typeOfSymbol = "option";
break;
case SecurityType.Future:
typeOfSymbol = "future";
break;
case SecurityType.Forex:
typeOfSymbol = "forex";
break;
case SecurityType.IndexOption:
typeOfSymbol = "option";
break;
default:
typeOfSymbol = "NotImplemented";
break;
}

if (typeOfSymbol == "NotImplemented")
{
_algorithm.Error($"{targetSymbol.SecurityType} security type is not supported by Collective2.");
return false;
}

return true;
}

/// <summary>
/// Converts a given percentage of a position into the number of shares of it
/// </summary>
Expand Down Expand Up @@ -332,6 +301,138 @@ private class DesiredPositionResponse
public List<long> CanceledSignals { get; set; } = new List<long>();
}

/// <summary>
/// Returns the given symbol in the expected C2 format
/// </summary>
private string GetSymbol(Symbol symbol)
{
if (CurrencyPairUtil.TryDecomposeCurrencyPair(symbol, out var baseCurrency, out var quoteCurrency))
{
return $"{baseCurrency}/{quoteCurrency}";
}
else if (symbol.SecurityType.IsOption())
{
return symbol.Underlying.Value;
}
else
{
return symbol.ID.Symbol;
}
}

private string GetMICExchangeCode(Symbol symbol)
{
if (_unknownMarketSymbols.Contains(symbol.Value) || symbol.SecurityType == SecurityType.Equity || symbol.SecurityType.IsOption())
{
return "DEFAULT";
}

switch (symbol.ID.Market)
{
case "india":
return "XNSE";
case "hkfe":
return "XHKF";
case "nyseliffe":
return "XNLI";
case "eurex":
return "XEUR";
case "ice":
return "IEPA";
case "cboe":
return "XCBO";
case "cfe":
return "XCBF";
case "cbot":
return "XCBT";
case "comex":
return "XCEC";
case "nymex":
return "XNYM";
case "sgx":
return "XSES";
case "fxcm":
return symbol.ID.Market.ToUpper();
case "ose":
case "cme":
return "X" + symbol.ID.Market.ToUpper();
default:
_unknownMarketSymbols.Add(symbol.Value);
_algorithm.Debug($"The market of the symbol {symbol.Value} was unexpected: {symbol.ID.Market}. Using 'DEFAULT' as market");
return "DEFAULT";
}
}

/// <summary>
/// Returns the given security type in the format C2 expects
/// </summary>
private string GetSecurityTypeAcronym(SecurityType securityType)
{
switch (securityType)
{
case SecurityType.Equity:
return "CS";
case SecurityType.Future:
return "FUT";
case SecurityType.Option:
case SecurityType.IndexOption:
return "OPT";
case SecurityType.Forex:
return "FOR";
default:
_algorithm.Error($"Unexpected security type found: {securityType}. Collective2 just accepts: Equity, Future, Option, Index Option and Stock");
return null;
}
}

/// <summary>
/// Returns the expiration date in the format C2 expects
/// </summary>
private string GetMaturityMonthYear(Symbol symbol)
{
var delistingDate = symbol.GetDelistingDate();
if (delistingDate == Time.EndOfTime) // The given symbol is equity or forex
{
return null;
}

if (delistingDate < _algorithm.Securities[symbol].LocalTime) // The given symbol has already expired
{
_algorithm.Error($"Instrument {symbol} has already expired. Its delisting date was: {delistingDate}. This signal won't be sent to Collective2.");
return string.Empty;
}

return $"{delistingDate:yyyyMMdd}";
}

private int? GetPutOrCallValue(Symbol symbol)
{
if (symbol.SecurityType.IsOption())
{
switch (symbol.ID.OptionRight)
{
case OptionRight.Put:
return 0;
case OptionRight.Call:
return 1;
}
}

return null;
}

private decimal? GetStrikePrice(Symbol symbol)
{
if (symbol.SecurityType.IsOption())
{
return symbol.ID.StrikePrice;
}
else
{
return null;
}
}

/// <summary>
/// The C2 ResponseStatus object
/// </summary>
Expand Down Expand Up @@ -393,34 +494,72 @@ protected class Collective2Position
/// <summary>
/// Position symbol
/// </summary>
[JsonProperty(PropertyName = "C2Symbol")]
public C2Symbol C2Symbol { get; set; }
[JsonProperty(PropertyName = "exchangeSymbol")]
public C2ExchangeSymbol ExchangeSymbol { get; set; }

/// <summary>
/// Number of shares/contracts of the given symbol. Positive quantites are long positions
/// and negative short positions.
/// </summary>
[JsonProperty(PropertyName = "Quantity")]
[JsonProperty(PropertyName = "quantity")]
public decimal Quantity { get; set; } // number of shares, not % of the portfolio
}

/// <summary>
/// The Collective2 symbol
/// </summary>
protected class C2Symbol
protected class C2ExchangeSymbol
{
/// <summary>
/// The The full native C2 symbol e.g. BSRR2121Q22.5
/// The exchange root symbol e.g. AAPL
/// </summary>
[JsonProperty(PropertyName = "symbol")]
public string Symbol { get; set; }

/// <summary>
/// The 3-character ISO instrument currency. E.g. 'USD'
/// </summary>
[JsonProperty(PropertyName = "currency")]
public string Currency { get; set; }

/// <summary>
/// The MIC Exchange code e.g. DEFAULT (for stocks & options),
/// XCME, XEUR, XICE, XLIF, XNYB, XNYM, XASX, XCBF, XCBT, XCEC,
/// XKBT, XSES. See details at http://www.iso15022.org/MIC/homepageMIC.htm
/// </summary>
[JsonProperty(PropertyName = "FullSymbol")]
public string FullSymbol { get; set; }
[JsonProperty(PropertyName = "securityExchange")]
public string SecurityExchange { get; set; }


/// <summary>
/// The type of instrument. e.g. 'stock', 'option', 'future', 'forex'
/// The SecurityType e.g. 'CS'(Common Stock), 'FUT' (Future), 'OPT' (Option), 'FOR' (Forex)
/// </summary>
[JsonProperty(PropertyName = "securityType")]
public string SecurityType { get; set; }

/// <summary>
/// The MaturityMonthYear e.g. '202103' (March 2021), or if the contract requires a day: '20210521' (May 21, 2021)
/// </summary>
[JsonProperty(PropertyName = "maturityMonthYear")]
public string MaturityMonthYear { get; set; }

/// <summary>
/// The Option PutOrCall e.g. 0 = Put, 1 = Call
/// </summary>
[JsonProperty(PropertyName = "putOrCall")]
public int? PutOrCall { get; set; }

/// <summary>
/// The ISO Option Strike Price. Zero means none
/// </summary>
[JsonProperty(PropertyName = "strikePrice")]
public decimal? StrikePrice { get; set; }

/// <summary>
/// The multiplier to apply to the Exchange price to get the C2-formatted price. Default is 1
/// </summary>
[JsonProperty(PropertyName = "SymbolType")]
public string SymbolType { get; set; }
[JsonProperty(PropertyName = "priceMultiplier")]
public decimal PriceMultiplier { get; set; } = 1;
}
}
}
Loading
Loading