Concatenate Instance attribute using class with input - python

I am super new to python so new to OOP and class (I am originally MATLAB user as an engineer...) so please teach me as much as possible.
Anyways I am trying to do the following.
Create a class called Stock - something like below
class Stock :
def __init__(self,estimate,earning)
self.estimate = estimate # estimation of quarterly earnings
self.earning = earning # actual quarterly earnings
JPM(JP Morgan stock name) = Stock(11.7,10.9)
However, the estimate and earning values are reported every quarter and I want to create a numerical vector for each. The idea is like below, but of course it does not work.
JPM.estimate(1) = 11.9 # the second quarter earnings value at index 1 of the estimate
JPM.estimate(2) = 12.1 # the third quarter earnings value at index 2 of the estimate
JPM.estimate(3) = XX.XX # and so on.
Using .estimate(#) is just to show what I want to do. Using .append() or other methods you would like to teach me is fine.
The reason I am trying to do it this way is because I need 3 vectors for one stock(and I have about 1000 stocks so at the end I would have 3000 vectors to take care of). So I am planning on creating an instance of a stock and having 3 vectors as instance attributes. (Hopefully I got the terminology right.)
earnings vector
estimate vector
the date those earnings were reported.
Am I using the class function wrong(as it was never intended to be used this way?) or what can I do to achieve such concatenation for instance attributes as the data are received from web scraping?

It is not at all clear what you are trying to do with the Stock Class, but if all you want to do is create a list of stock price and earnings organized by date, you could do the following :
from collections import namedtuple, defaultdict
# Create a easily referenced tuple for defining staock data
StockData = namedtuple('StockData', ['date', 'earn', 'est'])
class Stock:
def __init__(self, data: StockData) -> None:
self._quotes = defaultdict()
self._quotes[data.date] = (data.earn, data.est)
def add(self, data: StockData) -> None:
self._quotes[data.date] = (data.earn, data.est)
def value(self, date: str) -> tuple:
# return tuple of (Earnings, Estimate) for date if it exists, else KeyError
return self._quotes[date]
def __repr__(self):
return str(self._quotes)
To load the stock class with data, you can do something along the lines of:
stk = Stock(StockData('1/20/2021', 123.5, 124.0))
stk.add(StockData('6/23/2021', 132.7, 119.4))
print(stk) yields:
defaultdict(None, {'1/20/2021': (123.5, 124.0), '6/23/2021': (132.7, 119.4)})
and, stk.value('1/20/2021')​ yields (123.5, 124.0)

Related

How to save results (and recall them when needed) of a simulation in Python?

I started (based on the idea shown in this model an actuarial project in Python in which I want to simulate, based on a set of inputs and adding (as done here: https://github.com/Saurabh0503/Financial-modelling-and-valuationn/blob/main/Dynamic%20Salary%20Retirement%20Model%20Internal%20Randomness.ipynb) some degree of internal randomness, how much it will take for an individual to retire, with a certain amount of wealth and a certain amount of annual salary and by submitting a certain annual payment (calculated as the desired cash divided by the years that will be necessary to retire). In my model's variation, the user can define his/her own parameters, making the model more flexible and user friendly; and there is a function that calculates the desired retirement cash based on individual's propensity both to save and spend.
The problem is that since I want to summarize (by taking the mean, max, min and std. deviation of wealth, salary and years to retirement) the output I obtain from the model, I have to save results (and to recall them) when I need to do so; but I don't have idea of what to do in order to accomplish this task.
I tried this solution, consisting in saving the simultation's output in a pandas dataframe. In particular I wrote that function:
def get_salary_wealth_year_case_df(data):
all_ytrs = []
salary = []
wealth = []
annual_payments = []
for i in range(data.n_iter):
ytr = years_to_retirement(data, print_output=False)
sal = salary_at_year(data, year, case, print_output=False)
wlt = wealth_at_year(data, year, prior_wealth, case, print_output=False)
pmt = annual_pmts_case_df(wealth_at_year, year, case, print_output=False)
all_ytrs.append(ytr)
salary.append(sal)
annual_payments.append(pmt)
df = pd.DataFrame()
df['Years to Retirement'] = all_ytrs
df['Salary'] = sal
df['Wealth'] = wlt
df['Annual Payments'] = pmt
return df
I need a feedback about what I'm doing. Am I doing it right? If so, are there more efficient ways to do so? If not, what should I do? Thanks in advance!
Given the inputs used for the function, I'm assuming your code (as it is) will do just fine in terms of computation speed.
As suggested, you can add a saving option to your function so the results that are being returned are stored in a .csv file.
def get_salary_wealth_year_case_df(data, path):
all_ytrs = []
salary = []
wealth = []
annual_payments = []
for i in range(data.n_iter):
ytr = years_to_retirement(data, print_output=False)
sal = salary_at_year(data, year, case, print_output=False)
wlt = wealth_at_year(data, year, prior_wealth, case, print_output=False)
pmt = annual_pmts_case_df(wealth_at_year, year, case, print_output=False)
all_ytrs.append(ytr)
salary.append(sal)
annual_payments.append(pmt)
df = pd.DataFrame()
df['Years to Retirement'] = all_ytrs
df['Salary'] = sal
df['Wealth'] = wlt
df['Annual Payments'] = pmt
# Save the dataframe to a given path inside your workspace
df.to_csv(path, header=False)
return df
After saving, returning the object might be optional. This depends on if you are going to use this dataframe on your code moving forward.

How do you use a method parameter as a name for a new object? [duplicate]

This question already has answers here:
How do I create variable variables?
(17 answers)
Closed 12 months ago.
In this code I'm trying to create a portfolio object that has a method that will generate a new instance of the class Stock whenever a purchase is made. I would like that Stock object to have its ticker as its name/pointer.
So I actually would like it to be interpreted as AMZN = Stock() below, but I can't make this work. The output should be 3000. I have tried different methods with no success so would be grateful for some advice. Quite new at this so might be some complications I'm not aware of...
class Stock:
def __init__(self, ticker, price, amount):
self.ticker = ticker
self.price = price
self.amount = amount
# Create portfolio to hold stocks and available funds
class Portfolio:
def __init__(self, funds):
self.funds = funds
self.stockPortfolio = []
# Buying a stock and adding it to the portfolio
def buyStock(self, ticker, price, amount):
#Add stock to portfolio
self.stockPortfolio.append(ticker)
ticker = Stock(ticker, price, amount) # Would like this to be read as AMZN = Stock()
return
p = Portfolio(100000)
p.buyStock("AMZN", 3000, 20)
print(AMZN.amount)
Wouldn't you rather have AMZN be a member of the Portfolio object that you created? Consider the scenario where you have multiple portfolios all of which own AMZN stock. Whose AMZN holdings should AMZN.amount refer to?
Also, say I buy 20 shares of AMZN at 3000, and 20 more at 1500, Which of these purchases should it refer to? Should it instead give you a list of Stock objects for that ticker?
I suggest you define Portfolio.stockPortfolio as a collections.defaultdict object. Then, you can define a __getitem__() method of Portfolio to take the ticker symbol like p["AMZN"] and return the correct list from stockPortfolio. If you really want to allow access to the portfolio's holdings using attributes like p.AMZN, you could define the __getattr__() method. However, I strongly recommend you don't do this because of reasons given by #martineau in their comment above: Why you don't want to dynamically create variables and Keep data out of your variable names.
import collections
class Stock:
def __init__(self, ticker, price, amount):
self.ticker = ticker
self.price = price
self.amount = amount
def __repr__(self):
return f"Stock('{self.ticker}', {self.price}, {self.amount})"
# Create portfolio to hold stocks and available funds
class Portfolio:
def __init__(self, funds):
self.funds = funds
self.stockPortfolio = collections.defaultdict(list)
# Buying a stock and adding it to the portfolio
def buyStock(self, ticker, price, amount):
#Add stock to portfolio
stk = Stock(ticker, price, amount)
self.stockPortfolio[ticker].append(stk)
def __getitem__(self, ticker):
return self.stockPortfolio[ticker]
def __getattr__(self, ticker):
return self.stockPortfolio[ticker]
I added a __repr__ to Stock so that we can see what the stock objects in the lists contain.
p = Portfolio(100000)
p.buyStock("AMZN", 3000, 20)
print(p["AMZN"])
# Output: [Stock('AMZN', 3000, 20)]
p.buyStock("AMZN", 3500, 10)
print(p.AMZN)
# Output: [Stock('AMZN', 3000, 20), Stock('AMZN', 3500, 10)]
You can do what you want, but it's a pretty bad idea.
First, lets explain how to do it. You'll want to use the globals() builtin, which returns a dictionary to you holding all the global variables defined in the current module. If you edit the dictionary, the global variables will be changed. In your case, you want to add the Stock object as a value keyed by the ticker name. That would look like:
def buyStock(self, ticker, price, amount):
#Add stock to portfolio
self.stockPortfolio.append(ticker)
globals()[ticker] = Stock(ticker, price, amount) # make a global variable
Now that you know how to do it, here's why you probably shouldn't. The problem with using the global namespace like this is that you can't (easily) write code that expects a given stock ticker variable to exist. A line like your print(AMZN.amount) will fail if you bought a different stock instead. Similarly, if you buy another batch of Amazon shares, you'll overwrite the AMZN variable with a new Stock object, and will have lost track of how much your first purchase was of, and what price you paid for it.
A much better idea is to put the stock objects into a data structure, such as the list you have in your portfolio already. That way you don't need to use a specific name for it, and you can have more than one stock purchase with the same name, if necessary. I'd use something like this:
def buyStock(self, ticker, price, amount):
#Add stock to portfolio
self.stockPortfolio.append(Stock(ticker, price, amount))
You could print the amount of your latest purchase with print(p.stockPortfolio[-1].amount), or write a loop to print out all the stocks (and their amounts or values, maybe) instead of always just getting the first one.

How to filter Django objects based on value returned by a method?

I have an Django object with a method get_volume_sum(self) that return a float, and I would like to query the top n objects with the highest value, how can I do that?
For example I could do a loop like this but I would like a more elegant solution.
vol = []
obj = []
for m in Market.object.filter(**args): # 3000 objects
sum = m.get_volume_sum()
vol.append(sum)
obj.append(m.id)
top = search_top_n(obj, vol, n)
And this is how the method looks like:
# return sum of volume over last hours
def get_volume_sum(self, hours):
return Candle.objects.filter(market=self,
dt__gte=timezone.now()-timedelta(hours=hours)
).aggregate(models.Sum('vo'))
From what I see here even with Python there isn't a single line solution.
You should not filter with the method, this will result in an N+1 problem: for 3'000 Market objects, it will generate an additional 3'0000 queries to obtain the volumes.
You can do this in bulk with a .annotate(…) [Django-doc]:
from django.db.models import Sum
hours = 12 # some value for hours
Market.objects.filter(
**args,
candle__dt__gte=timezone.now()-timedelta(hours=hours),
).annotate(
candle_vol=Sum('candle__vo')
).order_by('-candle_vol')
Here there is however a small caveat: if there is no related Candle, then these Markets will be filtered out. We can prevent that by allowing also Markets without Candles with:
from django.db.models import Q, Sum
hours = 12 # some value for hours
Market.objects.filter(
Q(candle__dt__gte=timezone.now()-timedelta(hours=hours)) |
Q(candle=None),
**args
).annotate(
candle_vol=Sum('candle__vo')
).order_by('-candle_vol')

creating signals based on current and prior time periods

I'm trying to write a trading algo and I am very new to python.
Lots of things are easy to understand but I get lost easily. I have a strategy I want to use, but the coding is getting in the way.
I want to create two moving averages and when they cross I want that to be a signal.
The part im I am currently struggling with is also including information about the prior period.
When
MovingAverage1( last 10 candles ) == MovingAverage2( Last 20 candles ),
that's a signal,
but is it a buy or sell?
When
MovingAVerage1( last 10 candles after skipping most recent ) > MovingAverage2( last 10 candles after skipping most recent )
then sell.
Here is what I've got so far, where the MA-s I am using are being simplified for this question:
class MyMACrossStrategy (Strategy):
"""
Requires:
symbol - A stock symbol on which to form a strategy on.
bars - A DataFrame of bars for the above symbol.
short_window - Lookback period for short moving average.
long_window - Lookback period for long moving average."""
def __init__(self, symbol, bars, short_window=4, long_window=9):
self.symbol = symbol
self.bars = bars
self.short_window = short_window
self.long_window = long_window
# Function Helper for indicators
def fill_for_noncomputable_vals(input_data, result_data):
non_computable_values = np.repeat(
np.nan, len(input_data) - len(result_data)
)
filled_result_data = np.append(non_computable_values, result_data)
return filled_result_data
def simple_moving_average(data, period):
"""
Simple Moving Average.
Formula:
SUM(data / N)
"""
catch_errors.check_for_period_error(data, period)
# Mean of Empty Slice RuntimeWarning doesn't affect output so it is
# supressed
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=RuntimeWarning)
sma = [np.mean(data[idx-(period-1):idx+1]) for idx in range(0, len(data))]
sma = fill_for_noncomputable_vals(data, sma)
return sma
def hull_moving_average(data, period):
"""
Hull Moving Average.
Formula:
HMA = WMA(2*WMA(n/2) - WMA(n)), sqrt(n)
"""
catch_errors.check_for_period_error(data, period)
hma = wma(
2 * wma(data, int(period/2)) - wma(data, period), int(np.sqrt(period))
)
return hma
def generate_signals(self):
"""Returns the DataFrame of symbols containing the signals
to go long, short or hold (1, -1 or 0)."""
signals = pd.DataFrame(index=self.bars.index)
signals['signal'] = 0.0
# Create the set of moving averages over the
# respective periods
signals['Fast_Line'] = sma(bars['Close'], self.short_window)
signals['Slow_line'] = hma(bars['Close'], self.long_window)
signals1['Fast_Line'] = sma(bars['Close'], self.short_window[-1])
signals1['Slow_line'] = hma(bars['Close'], self.long_window[-1])
# Create a 'signal' (invested or not invested) when the short moving average crosses the long
# moving average, but only for the period greater than the shortest moving average window
signals['signal'][self.short_window:] = np.where(signals['Fast_Line'][self.short_window:]
> signals['Slow_line'][self.short_window:], 1.0, 0.0)
# Take the difference of the signals in order to generate actual trading orders
signals['positions'] = signals['signal'].diff()
if signals['Fast_Line'] = signals['Slow_Line'] and ...
return signals
Hopefully my question makes sense.
I am assuming that you want to test your strategy first before using it in live market. You can download the stock data from yahoo finance in csv format. And you can upload with below code:
import pandas as pd
import numpy as np
data = pd.read_csv('MSFT.csv')
once the data is stored in the pandas dataframe data, you can moving average of the Closing price with following code:
if you are planning the crossover strategy
sma_days=20
lma_days=50
data['SMA_20']=data['Close'].rolling(window=sma_days,center=False).mean()
data['SMA_50']=data['Close'].rolling(window=lma_days,center=False).mean()
data['SIGNAL']=np.where(data['SMA_20']>data['SMA_50'],'BUY','SELL')

How to price a SimpleCashFlow

I would like to use QuantLib to price a portfolio of liabilities, which are modeled to be deterministic future cash-flows. I am now modelling them as a strip of FixedRateBonds with zero coupons, which seems like a very inelegant solution.
Problem:
Question 1: Is there a way to create an 'Instrument' that is just a 'SimpleCashFlow', 'Redemption' etc. and price it on a discount curve?
Question 2: Is it possible to construct a 'CashFlows' object or Instrument from multiple SimpleCashFlow's and price it on a curve?
Many thanks in advance
Code Example:
See code below for an example of what I am trying to do.
from QuantLib import *
# set params
calc_date = Date(30, 3, 2017)
risk_free_rate = 0.01
discount_curve = YieldTermStructureHandle(
FlatForward(calc_date, risk_free_rate, ActualActual()))
bond_engine = DiscountingBondEngine(discount_curve)
# characteristics of the cash-flow that I am trying to NPV
paymentdate = Date(30, 3, 2018)
paymentamount = 1000
# this works: pricing a fixed rate bond with no coupons
schedule = Schedule(paymentdate-1, paymentdate, Period(Annual), TARGET(),
Unadjusted, Unadjusted, DateGeneration.Backward, False)
fixed_rate_bond = FixedRateBond(0, paymentamount, schedule, [0.0],ActualActual())
bond_engine = DiscountingBondEngine(discount_curve)
fixed_rate_bond.setPricingEngine(bond_engine)
print(fixed_rate_bond.NPV())
# create a simple cashflow
simple_cash_flow = SimpleCashFlow(paymentamount, paymentdate)
# Q1: how to create instrument, set pricing engine and price a SimpleCashFlow?
#wrongcode:# simple_cash_flow.setPricingEngine(bond_engine)
#wrongcode:# print(simple_cash_flow.NPV())
# Q2: can I stick multiple cashflows into a single instrument, e.g.:
# how do I construct and price a CashFlows object from multiple 'SimpleCashFlow's?
simple_cash_flow2 = SimpleCashFlow(paymentamount, Date(30, 3, 2019))
#wrongcode:# cashflows_multiple = CashFlows([simple_cash_flow, simple_cash_flow2])
#wrongcode:# cashflows_multiple.setPricingEngine(bond_engine)
#wrongcode:# print(cashflows_multiple.NPV())
There are a couple of possible approaches. If you want to use an instrument, you can use a ZeroCouponBond instead of the fixed-rate one you're currently using:
bond = ZeroCouponBond(0, TARGET(), paymentamount, paymentdate)
bond.setPricingEngine(bond_engine)
print(bond.NPV())
Using an instrument will give you notifications and recalculation if the discount curve were to change, but might be overkill if you want a single pricing. In that case, you might work directly with the cashflows by using the methods of the CashFlows class:
cf = SimpleCashFlow(paymentamount, paymentdate)
print(CashFlows.npv([cf], discount_curve, True))
where the last parameter is True if you want to include any cashflow happening on today's date and False otherwise (note that this will give you a result a bit different from your calculation; that's because the payment date you used is a TARGET holiday, and the FixedRateBond constructor adjusts it to the next business day).
The above also works with several cash flows:
cfs = [SimpleCashFlow(paymentamount, paymentdate),
SimpleCashFlow(paymentamount*0.5, paymentdate+180),
SimpleCashFlow(paymentamount*2, paymentdate+360)]
print(CashFlows.npv(cfs, discount_curve, True))
Finally, if you want to do the same with an instrument, you can use the base Bond class and pass the cashflows directly:
custom_bond = Bond(0, TARGET(), 100.0, Date(), Date(), cfs)
custom_bond.setPricingEngine(bond_engine)
print(custom_bond.NPV())
this works but is kind of a kludge: the bond uses the passed cashflows directly and ignores the passed face amount and maturity date.

Categories