d5c64dee54
Adds Lean Symbol object decoder. Given a string with ticker + security ID, returns some of the security properties (market, security type, etc) Closes #2909 Some users have the Symbol string object but don't know what the ID means. This python class can be used to translate the ID. Added unit test.
163 lines
7.0 KiB
Python
163 lines
7.0 KiB
Python
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
|
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
MARKETS = ['empty', 'USA', 'FXCM', 'Oanda', 'Dukascopy', 'Bitfinex', 'Globex', 'NYMEX', 'CBOT', 'ICE', 'CBOE', 'NSE',
|
|
'GDAX', 'Kraken', 'Bittrex', 'Bithumb', 'Binance', 'Poloniex', 'Coinone', 'HitBTC', 'OkCoin', 'Bitstamp']
|
|
|
|
SECURITY_TYPES = ['Base', 'Equity', 'Option', 'Commodity', 'Forex', 'Future', 'Cfd', 'Crypto']
|
|
|
|
OPTION_STYLES = ['American', 'European']
|
|
|
|
OPTION_RIGHTS = ['Call', 'Put']
|
|
|
|
|
|
class Symbol:
|
|
def __init__(self, security_id):
|
|
"""
|
|
Parses a Lean's SecurityIdentifier and decode its properties.
|
|
|
|
The SecurityIdentifier contains information about a specific security, this includes the symbol*, market,
|
|
security type (equity, future, etc.) and other data specific to the SecurityType.
|
|
|
|
* For equities, the SecurityIdentifier ticker is the first ticker symbol for which the security
|
|
traded. This is the first date mentioned in the map_files.
|
|
|
|
The Date property has different meaning fo different security types:
|
|
- For equities this is the first date the security traded. Technically speaking, in LEAN, this is the first
|
|
date mentioned in the map_files.
|
|
- For options this is the expiry date. For futures this is the settlement date.
|
|
- For forex and cfds this property will return None, as the field is not specified.
|
|
|
|
:param security_id: And string made of two components, the ticker and the unique SecurityIdentifier (sid),
|
|
separated by a space.
|
|
|
|
For securities with underlying, it can receive a pair of ticker-sid separated by an "|", the first represent
|
|
the security itself, the second is its underlying's SecurityIdentifier.
|
|
|
|
"""
|
|
security_type_width = 100
|
|
security_type_offset = 1
|
|
market_width = 1000
|
|
market_offset = security_type_offset * security_type_width
|
|
|
|
self.strike_default_scale = 4
|
|
self.strike_default_scaleExpanded = 10 ** self.strike_default_scale
|
|
self.strike_scale_width = 100
|
|
self.strike_scale_offset = market_offset * market_width
|
|
|
|
self.strike_width = 1000000
|
|
self.strike_offset = self.strike_scale_offset * self.strike_scale_width
|
|
|
|
option_style_width = 10
|
|
option_style_offset = self.strike_offset * self.strike_width
|
|
|
|
self.days_width = 100000
|
|
self.days_offset = option_style_offset * option_style_width
|
|
|
|
put_call_offset = self.days_offset * self.days_width
|
|
put_call_width = 10
|
|
|
|
self.ID = security_id
|
|
is_option = False
|
|
|
|
if '|' in security_id:
|
|
# If contains '|' means this security has an underlying.
|
|
[security_id, underlying_id] = security_id.split('|')
|
|
self.Underlying = Symbol(underlying_id)
|
|
is_option = True
|
|
|
|
symbol, properties = self.parse_security_id(security_id)
|
|
self.Symbol = symbol
|
|
self.SecurityType = SECURITY_TYPES[self.extract_from_properties(properties,
|
|
security_type_offset,
|
|
security_type_width)]
|
|
self.Market = MARKETS[self.extract_from_properties(properties,
|
|
market_offset,
|
|
market_width)]
|
|
|
|
if self.SecurityType == 'Equity' or self.SecurityType == 'Option' or self.SecurityType == 'Future':
|
|
self.Date = self.extract_date_from_properties(properties)
|
|
else:
|
|
self.Date = None
|
|
|
|
if is_option:
|
|
self.OptionRight = OPTION_RIGHTS[self.extract_from_properties(properties,
|
|
put_call_offset,
|
|
put_call_width)]
|
|
self.OptionStyle = OPTION_STYLES[self.extract_from_properties(properties,
|
|
option_style_offset,
|
|
option_style_width)]
|
|
self.StrikePrice = self.extract_strike_price_from_properties(properties)
|
|
|
|
@staticmethod
|
|
def extract_from_properties(properties, offset, width):
|
|
"""
|
|
Generic method to extract securities properties from the decoded sid.
|
|
"""
|
|
return (properties // offset) % width
|
|
|
|
@staticmethod
|
|
def decode_base_36(code):
|
|
"""
|
|
Decode a string in base 36.
|
|
:param code: string to decode
|
|
:return: an integer representing the decoded sid.
|
|
"""
|
|
base = 1
|
|
result = 0
|
|
ord_zero = ord('0')
|
|
ord_a = ord('A')
|
|
for char in code[::-1]:
|
|
ord_char = ord(char)
|
|
value = ord_char - ord_zero if ord_char <= 57 else ord_char - ord_a + 10
|
|
result += base * value
|
|
base *= 36
|
|
return result
|
|
|
|
def extract_date_from_properties(self, properties):
|
|
"""
|
|
Extract the date from the decoded sid.
|
|
|
|
:param properties: an integer representing the decoded sid.
|
|
:return: a datetime object with the specific security Date.
|
|
"""
|
|
days = (properties // self.days_offset) % self.days_width
|
|
return datetime(1899, 12, 30, 0, 0, 0) + timedelta(days=float(days))
|
|
|
|
def extract_strike_price_from_properties(self, properties):
|
|
"""
|
|
Extract the date from the decoded sid.
|
|
|
|
:param properties: an integer representing the decoded sid.
|
|
:return: a float with the specific strike price.
|
|
"""
|
|
scale = int((properties // self.strike_scale_offset) % self.strike_scale_width) - self.strike_default_scale
|
|
unscaled_price = (properties // self.strike_offset) % self.strike_width
|
|
return unscaled_price * 10 ** scale
|
|
|
|
def parse_security_id(self, security_id):
|
|
"""
|
|
Parses a single sid and return the ticker and the decoded sid.
|
|
:param security_id: And string made of two components, the ticker and the unique SecurityIdentifier (sid),
|
|
separated by a space.
|
|
:return: a tuple of ticker and decoded sid
|
|
"""
|
|
[symbol, code] = security_id.split(' ')
|
|
properties = self.decode_base_36(code)
|
|
return symbol, properties
|
|
|
|
def __eq__(self, other):
|
|
return self.ID == other.ID
|