Lean  $LEAN_TAG$
DynamicData.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Dynamic;
19 using System.Linq.Expressions;
20 using System.Reflection;
21 using Python.Runtime;
22 using QuantConnect.Util;
23 
24 namespace QuantConnect.Data
25 {
26  /// <summary>
27  /// Dynamic Data Class: Accept flexible data, adapting to the columns provided by source.
28  /// </summary>
29  /// <remarks>Intended for use with Quandl class.</remarks>
30  public abstract class DynamicData : BaseData, IDynamicMetaObjectProvider
31  {
32  private static readonly MethodInfo SetPropertyMethodInfo = typeof(DynamicData).GetMethod("SetProperty");
33  private static readonly MethodInfo GetPropertyMethodInfo = typeof(DynamicData).GetMethod("GetProperty");
34 
35  private readonly IDictionary<string, object> _snakeNameStorage = new Dictionary<string, object>();
36  private readonly IDictionary<string, object> _storage = new Dictionary<string, object>();
37 
38  /// <summary>
39  /// Get the metaObject required for Dynamism.
40  /// </summary>
41  public DynamicMetaObject GetMetaObject(Expression parameter)
42  {
43  return new GetSetPropertyDynamicMetaObject(parameter, this, SetPropertyMethodInfo, GetPropertyMethodInfo);
44  }
45 
46  /// <summary>
47  /// Sets the property with the specified name to the value. This is a case-insensitve search.
48  /// </summary>
49  /// <param name="name">The property name to set</param>
50  /// <param name="value">The new property value</param>
51  /// <returns>Returns the input value back to the caller</returns>
52  public object SetProperty(string name, object value)
53  {
54  // let's be polite and support snake name access for the given object value too
55  var snakeName = name.ToSnakeCase();
56  name = name.LazyToLower();
57 
58  if (name == "time")
59  {
60  if (value is PyObject pyobject)
61  {
62  Time = pyobject.As<DateTime>();
63  }
64  else
65  {
66  Time = (DateTime)value;
67  }
68  }
69  else if (name == "endtime" || name == "end_time")
70  {
71  if (value is PyObject pyobject)
72  {
73  EndTime = pyobject.As<DateTime>();
74  }
75  else
76  {
77  EndTime = (DateTime)value;
78  }
79  }
80  else if (name == "value")
81  {
82  if (value is PyObject pyobject)
83  {
84  Value = pyobject.As<decimal>();
85  }
86  else
87  {
88  Value = (decimal)value;
89  }
90  }
91  else if (name == "symbol")
92  {
93  if (value is string)
94  {
95  Symbol = SymbolCache.GetSymbol((string) value);
96  }
97  else
98  {
99  if (value is PyObject pyobject)
100  {
101  Symbol = pyobject.As<Symbol>();
102  }
103  else
104  {
105  Symbol = (Symbol)value;
106  }
107  }
108  }
109 
110  _storage[name] = value;
111  if (snakeName != name)
112  {
113  _snakeNameStorage[snakeName] = value;
114  }
115  return value;
116  }
117 
118  /// <summary>
119  /// Gets the property's value with the specified name. This is a case-insensitve search.
120  /// </summary>
121  /// <param name="name">The property name to access</param>
122  /// <returns>object value of BaseData</returns>
123  public object GetProperty(string name)
124  {
125  name = name.ToLowerInvariant();
126 
127  // redirect these calls to the base types properties
128  if (name == "time")
129  {
130  return Time;
131  }
132  if (name == "endtime")
133  {
134  return EndTime;
135  }
136  if (name == "value")
137  {
138  return Value;
139  }
140  if (name == "symbol")
141  {
142  return Symbol;
143  }
144  if (name == "price")
145  {
146  return Price;
147  }
148 
149  object value;
150  if (!_storage.TryGetValue(name, out value) && !_snakeNameStorage.TryGetValue(name, out value))
151  {
152  // let the user know the property name that we couldn't find
153  throw new KeyNotFoundException(
154  $"Property with name \'{name}\' does not exist. Properties: Time, Symbol, Value {string.Join(", ", _storage.Keys)}"
155  );
156  }
157 
158  return value;
159  }
160 
161  /// <summary>
162  /// Gets whether or not this dynamic data instance has a property with the specified name.
163  /// This is a case-insensitve search.
164  /// </summary>
165  /// <param name="name">The property name to check for</param>
166  /// <returns>True if the property exists, false otherwise</returns>
167  public bool HasProperty(string name)
168  {
169  return _storage.ContainsKey(name.ToLowerInvariant());
170  }
171 
172  /// <summary>
173  /// Gets the storage dictionary
174  /// Python algorithms need this information since DynamicMetaObject does not work
175  /// </summary>
176  /// <returns>Dictionary that stores the paramenters names and values</returns>
177  public IDictionary<string, object> GetStorageDictionary()
178  {
179  return _storage;
180  }
181 
182  /// <summary>
183  /// Return a new instance clone of this object, used in fill forward
184  /// </summary>
185  /// <remarks>
186  /// This base implementation uses reflection to copy all public fields and properties
187  /// </remarks>
188  /// <returns>A clone of the current object</returns>
189  public override BaseData Clone()
190  {
191  var clone = ObjectActivator.Clone(this);
192  foreach (var kvp in _storage)
193  {
194  // don't forget to add the dynamic members!
195  clone._storage.Add(kvp);
196  }
197  return clone;
198  }
199  }
200 }