/*
* 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.
*/
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Python.Runtime;
using QuantConnect.Data;
namespace QuantConnect.Securities
{
///
/// Provides access to a security's data via it's type. This implementation supports dynamic access
/// by type name.
///
public class DynamicSecurityData : IDynamicMetaObjectProvider
{
private static readonly MethodInfo SetPropertyMethodInfo = typeof(DynamicSecurityData).GetMethod("SetProperty");
private static readonly MethodInfo GetPropertyMethodInfo = typeof(DynamicSecurityData).GetMethod("GetProperty");
private readonly IRegisteredSecurityDataTypesProvider _registeredTypes;
private readonly ConcurrentDictionary _genericTypes = new ConcurrentDictionary();
private readonly SecurityCache _cache;
///
/// Initializes a new instance of the class
///
/// Provides all the registered data types for the algorithm
/// The security cache
public DynamicSecurityData(IRegisteredSecurityDataTypesProvider registeredTypes, SecurityCache cache)
{
_registeredTypes = registeredTypes;
_cache = cache;
}
/// Returns the responsible for binding operations performed on this object.
/// The to bind this object.
/// The expression tree representation of the runtime value.
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return new GetSetPropertyDynamicMetaObject(parameter, this, SetPropertyMethodInfo, GetPropertyMethodInfo);
}
///
/// Gets whether or not this dynamic data instance has data stored for the specified type
///
public bool HasData()
{
return _cache.HasData(typeof(T));
}
///
/// Gets whether or not this dynamic data instance has a property with the specified name.
/// This is a case-insensitive search.
///
/// The property name to check for
/// True if the property exists, false otherwise
public bool HasProperty(string name)
{
Type type;
if (_registeredTypes.TryGetType(name, out type))
{
return _cache.HasData(type);
}
return false;
}
///
/// Gets the last item in the data list for the specified type
///
public T Get()
{
var list = GetAll();
return list.LastOrDefault();
}
///
/// Gets the data list for the specified type
///
public IReadOnlyList GetAll()
{
return GetAllImpl(typeof(T));
}
///
/// Get the matching cached object in a python friendly accessor
///
/// Type to search for
/// Matching object
public PyObject Get(Type type)
{
var list = GetAll(type);
if (list.Count == 0)
{
return null;
}
using (Py.GIL())
{
return list[list.Count - 1].ToPython();
}
}
///
/// Get all the matching types with a python friendly overload.
///
/// Search type
/// List of matching objects cached
public IList GetAll(Type type)
{
return GetAllImpl(type);
}
///
/// Sets the property with the specified name to the value. This is a case-insensitve search.
///
/// The property name to set
/// The new property value
/// Returns the input value back to the caller
[Obsolete("DynamicSecurityData is a view of the SecurityCache. It is readonly, properties can not be set")]
public object SetProperty(string name, object value)
{
throw new InvalidOperationException(Messages.DynamicSecurityData.PropertiesCannotBeSet);
}
///
/// Gets the property's value with the specified name. This is a case-insensitve search.
///
/// The property name to access
/// object value of BaseData
public object GetProperty(string name)
{
// check to see if the requested name matches one of the algorithm registered data types and if
// so, we'll return a new empty list. this precludes us from always needing to check HasData
Type type;
if (_registeredTypes.TryGetType(name, out type))
{
IReadOnlyList data;
if (_cache.TryGetValue(type, out data))
{
return data;
}
var listType = GetGenericListType(type);
return Activator.CreateInstance(listType);
}
throw new KeyNotFoundException(Messages.DynamicSecurityData.PropertyNotFound(name));
}
///
/// Get all implementation that covers both Python and C#
///
private dynamic GetAllImpl(Type type)
{
var data = GetProperty(type.Name);
var dataType = data.GetType();
if (dataType.GetElementType() == type // covers arrays
// covers lists
|| dataType.GenericTypeArguments.Length == 1
&& dataType.GenericTypeArguments[0] == type)
{
return data;
}
var baseDataList = data as IReadOnlyList;
if (baseDataList != null)
{
var listType = GetGenericListType(type);
var list = (IList)Activator.CreateInstance(listType);
foreach (var baseData in baseDataList)
{
list.Add(baseData);
}
return list;
}
throw new InvalidOperationException(Messages.DynamicSecurityData.UnexpectedTypesForGetAll(type, data));
}
private Type GetGenericListType(Type type)
{
Type containerType;
if (!_genericTypes.TryGetValue(type, out containerType))
{
// for performance we keep the generic type
_genericTypes[type] = containerType = typeof(List<>).MakeGenericType(type);
}
return containerType;
}
}
}