Custom Universes

CSV Format Example

Introduction

This page explains how to import custom data for universe selection sourced in CSV format.

Data Format

You must create a file with data in CSV format. Ensure the data in the file is in chronological order.

20170704,SPY,QQQ,FB,AAPL,IWM
20170706,QQQ,AAPL,IWM,FB,GOOGL
20170707,IWM,AAPL,FB,BAC,GOOGL
...
20170729,SPY,QQQ,FB,AAPL,IWM
20170801,QQQ,FB,AAPL,IWM,GOOGL
20170802,QQQ,IWM,FB,BAC,GOOGL

Define Custom Types

To define a custom data type, inherit the BaseDataPythonData class and override the GetSource and Reader methods.

public class StockDataSource : BaseData
{
    public List<Symbol> Symbols { get; set; } = [];
    public override DateTime EndTime => Time.AddDays(1);
    public string Line { get; set; }

    public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
    {
        if (!isLiveMode)
        {
            return new SubscriptionDataSource("csv-universe-example.csv", SubscriptionTransportMedium.ObjectStore, FileFormat.Csv);
        }
        return new SubscriptionDataSource("https://raw.githubusercontent.com/QuantConnect/Documentation/master/Resources/datasets/custom-data/csv-universe-example.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv);
    }

    public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
    {
        var csv = line.Split(',');
        var stocks = new StockDataSource { Symbol = config.Symbol, Line = line };

        try
        {
            stocks.Time = isLiveMode
                ? DateTime.UtcNow.ConvertFromUtc(config.DataTimeZone) 
                : DateTime.ParseExact(csv[0], "yyyyMMdd", null);

            stocks.Symbols.AddRange(csv.Skip(1).Select(ticker => 
            {
                // The tickers are point-in-time. We generate its security identifier for a given date
                // Then we create a Symbol where the Value is the ticker
                var sid = SecurityIdentifier.GenerateEquity(ticker, Market.USA, mappingResolveDate: stocks.Time);
                return new Symbol(sid, ticker);
            }));
        }
        catch { }
        return stocks;
}
class StockDataSource(PythonData):
    def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool) -> SubscriptionDataSource:
        if not is_live:
            return SubscriptionDataSource("csv-universe-example.csv", SubscriptionTransportMedium.OBJECT_STORE, FileFormat.CSV)
        return SubscriptionDataSource("https://raw.githubusercontent.com/QuantConnect/Documentation/master/Resources/datasets/custom-data/csv-universe-example.csv", SubscriptionTransportMedium.REMOTE_FILE, FileFormat.CSV)

    def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live: bool) -> BaseData:
        csv = line.split(',')
        stocks = StockDataSource()
        stocks.symbol = config.symbol
        stocks.line = line
        try:
            stocks.time = Extensions.convert_from_utc(datetime.utcnow(), config.data_time_zone) \
                if is_live else datetime.strptime(csv[0], "%Y%m%d")
            def point_in_time(ticker):
                # The tickers are point-in-time. We generate its security identifier for a given date
                # Then we create a Symbol where the value is the ticker
                sid = SecurityIdentifier.generate_equity(ticker, Market.USA, mapping_resolve_date=stocks.time)
                return Symbol(sid, ticker)
            stocks.symbols = [point_in_time (ticker) for ticker in csv[1:]]
        except:
            pass
        return stocks

Initialize Universe

To perform a universe selection with custom data, in the Initializeinitialize method, call the AddUniverseadd_universe method.

public class MyAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        AddUniverse<StockDataSource>(FilterFunction);
    }
}
class MyAlgorithm(QCAlgorithm): 
    def initialize(self) -> None:
        self.add_universe(StockDataSource, self._filter_function)

Receive Custom Data

As your data reader reads your custom data file, LEAN adds the data points into a List[StockDataSource])IEnumerable<StockDataSource> object it passes to your algorithm's filter function. Your filter function needs to return a list of Symbol or strstring object. LEAN automatically subscribes to these new assets and adds them to your algorithm.

public class MyAlgorithm : QCAlgorithm
{
    private IEnumerable<Symbol> FilterFunction(IEnumerable<BaseData> data)
    {
        var stockDataSource = data.OfType<StockDataSource>();
        return stockDataSource.SelectMany(x => x.Symbols);
    }

    public override void OnSecuritiesChanged(SecurityChanges changes)
    {
        Debug(changes.ToString());
    }
}
class MyAlgorithm(QCAlgorithm):
    def _filter_function(self, data: list[BaseData]) -> list[str]:
        symbols = []
        for item in data:
            symbols.extend(item.symbols)
        return symbols
    
    def on_securities_changed(self, changes: SecurityChanges) -> None:
        self.debug(str(changes))
    

If you add custom properties to your data object in the Readerreader method, LEAN adds them as members to the data object in your filter method. To ensure the property names you add in the Readerreader method follow the convention of member names, LEAN applies the following changes to the property names you provide in the Readerreader method:

  1. - and . characters are replaced with whitespace.
  2. The first letter is capitalized.
  3. Whitespace characters are removed.

For example, if you set a property name in the Readerreader method to ['some-property.name'], you can access it in your filter method through the Somepropertyname member of your data object.

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: