import csv
import datetime
import logging
import math
from decimal import Decimal
import pandas as pd
import random
import requests
import time
from db_calls import db_get
from db_calls_card_creation import *
from bs4 import BeautifulSoup
D20_CHANCES = {
'2': {
'chances': 1,
'inc': .05
},
'3': {
'chances': 2,
'inc': .1
},
'4': {
'chances': 3,
'inc': .15
},
'5': {
'chances': 4,
'inc': .2
},
'6': {
'chances': 5,
'inc': .25
},
'7': {
'chances': 6,
'inc': .3
},
'8': {
'chances': 5,
'inc': .25
},
'9': {
'chances': 4,
'inc': .2
},
'10': {
'chances': 3,
'inc': .15
},
'11': {
'chances': 2,
'inc': .1
},
'12': {
'chances': 1,
'inc': .05
}
}
BLANK_RESULTS = {
'vL': {
'1': {
'2': {
'result': None,
'splits': None,
'2d6': None
},
'3': {
'result': None,
'splits': None,
'2d6': None
},
'4': {
'result': None,
'splits': None,
'2d6': None
},
'5': {
'result': None,
'splits': None,
'2d6': None
},
'6': {
'result': None,
'splits': None,
'2d6': None
},
'7': {
'result': None,
'splits': None,
'2d6': None
},
'8': {
'result': None,
'splits': None,
'2d6': None
},
'9': {
'result': None,
'splits': None,
'2d6': None
},
'10': {
'result': None,
'splits': None,
'2d6': None
},
'11': {
'result': None,
'splits': None,
'2d6': None
},
'12': {
'result': None,
'splits': None,
'2d6': None
},
'splits': 0
},
'2': {
'2': {
'result': None,
'splits': None,
'2d6': None
},
'3': {
'result': None,
'splits': None,
'2d6': None
},
'4': {
'result': None,
'splits': None,
'2d6': None
},
'5': {
'result': None,
'splits': None,
'2d6': None
},
'6': {
'result': None,
'splits': None,
'2d6': None
},
'7': {
'result': None,
'splits': None,
'2d6': None
},
'8': {
'result': None,
'splits': None,
'2d6': None
},
'9': {
'result': None,
'splits': None,
'2d6': None
},
'10': {
'result': None,
'splits': None,
'2d6': None
},
'11': {
'result': None,
'splits': None,
'2d6': None
},
'12': {
'result': None,
'splits': None,
'2d6': None
},
'splits': 0
},
'3': {
'2': {
'result': None,
'splits': None,
'2d6': None
},
'3': {
'result': None,
'splits': None,
'2d6': None
},
'4': {
'result': None,
'splits': None,
'2d6': None
},
'5': {
'result': None,
'splits': None,
'2d6': None
},
'6': {
'result': None,
'splits': None,
'2d6': None
},
'7': {
'result': None,
'splits': None,
'2d6': None
},
'8': {
'result': None,
'splits': None,
'2d6': None
},
'9': {
'result': None,
'splits': None,
'2d6': None
},
'10': {
'result': None,
'splits': None,
'2d6': None
},
'11': {
'result': None,
'splits': None,
'2d6': None
},
'12': {
'result': None,
'splits': None,
'2d6': None
},
'splits': 0
}
},
'vR': {
'1': {
'2': {
'result': None,
'splits': None,
'2d6': None
},
'3': {
'result': None,
'splits': None,
'2d6': None
},
'4': {
'result': None,
'splits': None,
'2d6': None
},
'5': {
'result': None,
'splits': None,
'2d6': None
},
'6': {
'result': None,
'splits': None,
'2d6': None
},
'7': {
'result': None,
'splits': None,
'2d6': None
},
'8': {
'result': None,
'splits': None,
'2d6': None
},
'9': {
'result': None,
'splits': None,
'2d6': None
},
'10': {
'result': None,
'splits': None,
'2d6': None
},
'11': {
'result': None,
'splits': None,
'2d6': None
},
'12': {
'result': None,
'splits': None,
'2d6': None
},
'splits': 0
},
'2': {
'2': {
'result': None,
'splits': None,
'2d6': None
},
'3': {
'result': None,
'splits': None,
'2d6': None
},
'4': {
'result': None,
'splits': None,
'2d6': None
},
'5': {
'result': None,
'splits': None,
'2d6': None
},
'6': {
'result': None,
'splits': None,
'2d6': None
},
'7': {
'result': None,
'splits': None,
'2d6': None
},
'8': {
'result': None,
'splits': None,
'2d6': None
},
'9': {
'result': None,
'splits': None,
'2d6': None
},
'10': {
'result': None,
'splits': None,
'2d6': None
},
'11': {
'result': None,
'splits': None,
'2d6': None
},
'12': {
'result': None,
'splits': None,
'2d6': None
},
'splits': 0
},
'3': {
'2': {
'result': None,
'splits': None,
'2d6': None
},
'3': {
'result': None,
'splits': None,
'2d6': None
},
'4': {
'result': None,
'splits': None,
'2d6': None
},
'5': {
'result': None,
'splits': None,
'2d6': None
},
'6': {
'result': None,
'splits': None,
'2d6': None
},
'7': {
'result': None,
'splits': None,
'2d6': None
},
'8': {
'result': None,
'splits': None,
'2d6': None
},
'9': {
'result': None,
'splits': None,
'2d6': None
},
'10': {
'result': None,
'splits': None,
'2d6': None
},
'11': {
'result': None,
'splits': None,
'2d6': None
},
'12': {
'result': None,
'splits': None,
'2d6': None
},
'splits': 0
}
}
}
TESTING = False
YES = ['y', 'yes', 'yeet', 'please', 'yeah']
async def pd_players_df(cardset_id: int):
p_query = await db_get(
'players',
params=[('inc_dex', False), ('cardset_id', cardset_id), ('short_output', True)]
)
if p_query['count'] == 0:
raise ValueError(f'No players returned from Paper Dynasty API')
return pd.DataFrame(p_query['players'])
async def pd_battingcards_df(cardset_id: int):
bc_query = await db_get('battingcards', params=[('cardset_id', cardset_id), ('short_output', True)])
if bc_query['count'] == 0:
raise ValueError(f'No batting cards returned from Paper Dynasty API')
return pd.DataFrame(bc_query['cards']).rename(columns={'id': 'battingcard_id', 'player': 'player_id'})
async def pd_pitchingcards_df(cardset_id: int):
bc_query = await db_get('pitchingcards', params=[('cardset_id', cardset_id), ('short_output', True)])
if bc_query['count'] == 0:
raise ValueError(f'No pitching cards returned from Paper Dynasty API')
return pd.DataFrame(bc_query['cards']).rename(columns={'id': 'pitchingcard_id', 'player': 'player_id'})
async def pd_battingcardratings_df(cardset_id: int):
vl_query = await db_get(
'battingcardratings', params=[('cardset_id', cardset_id), ('vs_hand', 'L'), ('short_output', True)])
vr_query = await db_get(
'battingcardratings', params=[('cardset_id', cardset_id), ('vs_hand', 'R'), ('short_output', True)])
if 0 in [vl_query['count'], vr_query['count']]:
raise ValueError(f'No batting card ratings returned from Paper Dynasty API')
vl = pd.DataFrame(vl_query['ratings'])
vr = pd.DataFrame(vr_query['ratings'])
ratings = (pd.merge(vl, vr, on='battingcard', suffixes=('_vL', '_vR'))
.rename(columns={'battingcard': 'battingcard_id'}))
def get_total_ops(df_data):
ops_vl = df_data['obp_vL'] + df_data['slg_vL']
ops_vr = df_data['obp_vR'] + df_data['slg_vR']
return (ops_vr + ops_vl + min(ops_vl, ops_vr)) / 3
ratings['total_OPS'] = ratings.apply(get_total_ops, axis=1)
def new_rarity_id(df_data):
if df_data['total_OPS'] >= 1.2:
return 99
elif df_data['total_OPS'] >= 1:
return 1
elif df_data['total_OPS'] >= .9:
return 2
elif df_data['total_OPS'] >= .8:
return 3
elif df_data['total_OPS'] >= .7:
return 4
else:
return 5
ratings['new_rarity_id'] = ratings.apply(new_rarity_id, axis=1)
return ratings
# return pd.DataFrame(bcr_query['ratings']).rename(columns={'battingcard': 'battingcard_id'})
async def pd_pitchingcardratings_df(cardset_id: int):
vl_query = await db_get(
'pitchingcardratings', params=[('cardset_id', cardset_id), ('vs_hand', 'L'), ('short_output', True)])
vr_query = await db_get(
'pitchingcardratings', params=[('cardset_id', cardset_id), ('vs_hand', 'R'), ('short_output', True)])
if 0 in [vl_query['count'], vr_query['count']]:
raise ValueError(f'No pitching card ratings returned from Paper Dynasty API')
vl = pd.DataFrame(vl_query['ratings'])
vr = pd.DataFrame(vr_query['ratings'])
ratings = (pd.merge(vl, vr, on='pitchingcard', suffixes=('_vL', '_vR'))
.rename(columns={'pitchingcard': 'pitchingcard_id'}))
def get_total_ops(df_data):
ops_vl = df_data['obp_vL'] + df_data['slg_vL']
ops_vr = df_data['obp_vR'] + df_data['slg_vR']
return (ops_vr + ops_vl + max(ops_vl, ops_vr)) / 3
ratings['total_OPS'] = ratings.apply(get_total_ops, axis=1)
return ratings
async def pd_positions_df(cardset_id: int):
pos_query = await db_get(
'cardpositions', params=[('cardset_id', cardset_id), ('short_output', True), ('sort', 'innings-desc')])
if pos_query['count'] == 0:
raise ValueError('No position ratings returned from Paper Dynasty API')
all_pos = pd.DataFrame(pos_query['positions']).rename(columns={'player': 'player_id'})
return all_pos
def get_batting_stats(file_path: str = None, start_date: datetime.datetime = None, end_date: datetime.datetime = None):
if file_path is not None:
vl_basic = pd.read_csv(f'{file_path}vlhp-basic.csv').query('PA >= 20')
vr_basic = pd.read_csv(f'{file_path}vrhp-basic.csv').query('PA >= 40')
total_basic = pd.merge(vl_basic, vr_basic, on="playerId", suffixes=('_vL', '_vR'))
vl_rate = pd.read_csv(f'{file_path}vlhp-rate.csv').query('PA >= 20')
vr_rate = pd.read_csv(f'{file_path}vrhp-rate.csv').query('PA >= 40')
total_rate = pd.merge(vl_rate, vr_rate, on="playerId", suffixes=('_vL', '_vR'))
return pd.merge(total_basic, total_rate, on="playerId", suffixes=('', '_rate'))
else:
raise LookupError(f'Date-based stat pulls not implemented, yet. Please provide batting csv files.')
def get_pitching_stats(file_path: str = None, start_date: datetime.datetime = None, end_date: datetime.datetime = None):
if file_path is not None:
vl_basic = pd.read_csv(f'{file_path}vlhh-basic.csv').query('TBF >= 20')
vr_basic = pd.read_csv(f'{file_path}vrhh-basic.csv').query('TBF >= 40')
total_basic = pd.merge(vl_basic, vr_basic, on="playerId", suffixes=('_vL', '_vR'))
vl_rate = pd.read_csv(f'{file_path}vlhh-rate.csv').query('TBF >= 20')
vr_rate = pd.read_csv(f'{file_path}vrhh-rate.csv').query('TBF >= 40')
total_rate = pd.merge(vl_rate, vr_rate, on="playerId", suffixes=('_vL', '_vR'))
return pd.merge(total_basic, total_rate, on="playerId", suffixes=('', '_rate'))
else:
raise LookupError(f'Date-based stat pulls not implemented, yet. Please provide batting csv files.')
# vrb_url = f'https://www.fangraphs.com/leaders/splits-leaderboards?splitArr=6&splitArrPitch=&position=P' \
# f'&autoPt=false&splitTeams=false&statType=player&statgroup=1' \
# f'&startDate={start_date.year}-{start_date.month}-{start_date.day}' \
# f'&endDate={end_date.year}-{end_date.month}-{end_date.day}' \
# f'&players=&filter=&groupBy=season&sort=4,1&wxTemperature=&wxPressure=&wxAirDensity=' \
# f'&wxElevation=&wxWindSpeed='
# vrr_url = f'https://www.fangraphs.com/leaders/splits-leaderboards?splitArr=6&splitArrPitch=&position=P' \
# f'&autoPt=false&splitTeams=false&statType=player&statgroup=3' \
# f'&startDate={start_date.year}-{start_date.month}-{start_date.day}' \
# f'&endDate={end_date.year}-{end_date.month}-{end_date.day}' \
# f'&players=&filter=&groupBy=season&sort=4,1&wxTemperature=&wxPressure=&wxAirDensity=' \
# f'&wxElevation=&wxWindSpeed='
# vlb_url = f'https://www.fangraphs.com/leaders/splits-leaderboards?splitArr=5&splitArrPitch=&position=P' \
# f'&autoPt=false&splitTeams=false&statType=player&statgroup=1' \
# f'&startDate={start_date.year}-{start_date.month}-{start_date.day}' \
# f'&endDate={end_date.year}-{end_date.month}-{end_date.day}' \
# f'&players=&filter=&groupBy=season&sort=4,1&wxTemperature=&wxPressure=&wxAirDensity=' \
# f'&wxElevation=&wxWindSpeed='
# vlr_url = f'https://www.fangraphs.com/leaders/splits-leaderboards?splitArr=5&splitArrPitch=&position=P' \
# f'&autoPt=false&splitTeams=false&statType=player&statgroup=3' \
# f'&startDate={start_date.year}-{start_date.month}-{start_date.day}' \
# f'&endDate={end_date.year}-{end_date.month}-{end_date.day}' \
# f'&players=&filter=&groupBy=season&sort=4,1&wxTemperature=&wxPressure=&wxAirDensity=' \
# f'&wxElevation=&wxWindSpeed='
#
# soup = BeautifulSoup(requests.get(vrb_url).text, 'html.parser')
# time.sleep(3)
# table = soup.find('a', {'class': 'data-export'})
def get_pitching_peripherals(season: int):
url = f'https://www.baseball-reference.com/leagues/majors/{season}-standard-pitching.shtml'
soup = BeautifulSoup(requests.get(url).text, 'html.parser')
time.sleep(3)
table = soup.find('table', {'id': 'players_standard_pitching'})
headers = []
data = []
indeces = []
for row in table.find_all('tr'):
row_data = []
col_names = []
for cell in row.find_all('td'):
try:
player_id = cell['data-append-csv']
row_data.append(player_id)
if len(headers) == 0:
col_names.append('key_bbref')
except Exception as e:
pass
row_data.append(cell.text)
if len(headers) == 0:
col_names.append(cell['data-stat'])
if len(row_data) > 0:
data.append(row_data)
indeces.append(row_data[0])
if len(headers) == 0:
headers.extend(col_names)
pit_frame = pd.DataFrame(data, index=indeces, columns=headers).query('key_bbref == key_bbref')
return pit_frame.drop_duplicates(subset=['key_bbref'], keep='first')
def mround(x, prec=2, base=.05):
return round(base * round(float(x) / base), prec)
def chances_from_row(row_num):
if row_num == '2' or row_num == '12':
return 1
if row_num == '3' or row_num == '11':
return 2
if row_num == '4' or row_num == '10':
return 3
if row_num == '5' or row_num == '9':
return 4
if row_num == '6' or row_num == '8':
return 5
if row_num == '7':
return 6
raise ValueError(f'No chance count found for row_num {row_num}')
def legal_splits(tot_chances):
legal_2d6 = []
for x in D20_CHANCES:
num_incs = mround(tot_chances) / D20_CHANCES[x]['inc']
if num_incs - int(num_incs) == 0 and int(20 - num_incs) > 0:
legal_2d6.append({
'2d6': int(x),
'incs': int(num_incs),
'bad_chances': mround(D20_CHANCES[x]['chances'] * (int(20 - num_incs) / 20)),
'bad_incs': int(20 - num_incs)
})
random.shuffle(legal_2d6)
# if TESTING: print(f'tot_chances: {myround(tot_chances)}')
# if TESTING: print(f'legal_2d6: {legal_2d6}')
return legal_2d6
def result_string(tba_data, row_num, split_min=None, split_max=None):
bold1 = f'{"" if tba_data["bold"] else ""}'
bold2 = f'{"" if tba_data["bold"] else ""}'
row_string = f'{" " if int(row_num) < 10 else ""}{row_num}'
if TESTING: print(f'adding {tba_data["string"]} to row {row_num} / '
f'split_min: {split_min} / split_max: {split_max}')
# No splits; standard result
if not split_min:
return f'{bold1}{row_string}-{tba_data["string"]}{bold2}'
# With splits
split_nums = f'{split_min if split_min != 20 else ""}{"-" if split_min != 20 else ""}{split_max}'
data_string = tba_data["sm-string"] if "sm-string" in tba_data.keys() else tba_data["string"]
spaces = 18 - len(data_string) - len(split_nums)
if 'WALK' in data_string:
spaces -= 3
elif 'SI**' in data_string:
spaces += 1
elif 'DO*' in data_string:
spaces -= 1
elif 'DO*' in data_string:
spaces -= 2
elif 'so' in data_string:
spaces += 3
elif 'gb' in data_string:
spaces -= 3
if TESTING: print(f'len(tba_data["string"]): {len(data_string)} / len(split_nums): {len(split_nums)} '
f'spaces: {spaces}')
if split_min == 1 or split_min is None:
row_output = f'{row_string}-'
else:
row_output = ' '
if TESTING: print(f'row_output: {row_output}')
return f'{bold1}{row_output}{data_string}{" " * spaces}{split_nums}{bold2}'
def result_data(tba_data, row_num, tba_data_bottom=None, top_split_max=None, fatigue=False):
ret_data = {}
top_bold1 = f'{"" if tba_data["bold"] else ""}'
top_bold2 = f'{"" if tba_data["bold"] else ""}'
bot_bold1 = None
bot_bold2 = None
if tba_data_bottom:
bot_bold1 = f'{"" if tba_data_bottom["bold"] else ""}'
bot_bold2 = f'{"" if tba_data_bottom["bold"] else ""}'
if tba_data_bottom is None:
ret_data['2d6'] = f'{top_bold1}{int(row_num)}-{top_bold2}'
ret_data['splits'] = f'{top_bold1}{top_bold2}'
ret_data['result'] = f'{top_bold1}' \
f'{tba_data["string"]}{" •" if fatigue else ""}' \
f'{top_bold2}'
else:
ret_data['2d6'] = f'{top_bold1}{int(row_num)}-{top_bold2}\n'
ret_data['splits'] = f'{top_bold1}1{"-" if top_split_max != 1 else ""}' \
f'{top_split_max if top_split_max != 1 else ""}{top_bold2}\n' \
f'{bot_bold1}{top_split_max+1}{"-20" if top_split_max != 19 else ""}{bot_bold2}'
ret_data['result'] = \
f'{top_bold1}{tba_data["sm-string"] if "sm-string" in tba_data.keys() else tba_data["string"]}' \
f'{top_bold2}\n' \
f'{bot_bold1}' \
f'{tba_data_bottom["sm-string"] if "sm-string" in tba_data_bottom.keys() else tba_data_bottom["string"]}' \
f'{bot_bold2}'
return ret_data
def get_of(batter_hand, pitcher_hand, pull_side=True):
if batter_hand == 'R':
return 'lf' if pull_side else 'rf'
if batter_hand == 'L':
return 'rf' if pull_side else 'lf'
if batter_hand == 'S':
if pitcher_hand == 'L':
return 'rf' if pull_side else 'rf'
else:
return 'lf' if pull_side else 'lf'
def get_col(col_num):
if col_num == '1':
return 'one'
if col_num == '2':
return 'two'
if col_num == '3':
return 'three'
def write_to_csv(output_path, file_name: str, row_data: list):
# Build the csv output
fpath = (output_path / f'{file_name}').with_suffix('.csv')
# logging.info(f'Printing following data to {file_name}:\n\n{row_data}')
with fpath.open(mode='w+', newline='', encoding='utf-8') as csv_File:
writer = csv.writer(csv_File)
writer.writerows(row_data)
def get_position_string(all_pos: list, inc_p: bool):
if len(all_pos) == 0:
return 'dh'
of_arm = None
of_error = None
of_innings = None
lf_range = None
lf_innings = 0
cf_range = None
cf_innings = 0
rf_range = None
rf_innings = 0
all_def = []
for x in all_pos:
if x.position == 'OF':
of_arm = f'{"+" if "-" not in x.arm else ""}{x.arm}'
of_error = x.error
of_innings = x.innings
elif x.position == 'CF':
cf_range = x.range
cf_innings = x.innings
elif x.position == 'LF':
lf_range = x.range
lf_innings = x.innings
elif x.position == 'RF':
rf_range = x.range
rf_innings = x.innings
elif x.position == 'C':
all_def.append(
(f'c-{x.range}({"+" if int(x.arm) >= 0 else ""}{x.arm}) e{x.error} T-{x.overthrow}(pb-{x.pb})', x.innings)
)
elif 'P' in x.position and not inc_p:
pass
else:
all_def.append((f'{x.position.lower()}-{x.range}e{x.error}', x.innings))
if of_arm is not None:
logging.info(
f'\n\nProcessing OF player ID {all_pos[0].player_id}\nlf-{lf_range} / cf-{cf_range} / rf-{rf_range}'
)
all_of = []
if lf_innings > 0:
all_of.append((lf_range, lf_innings, 'lf'))
if cf_innings > 0:
all_of.append((cf_range, cf_innings, 'cf'))
if rf_innings > 0:
all_of.append((rf_range, rf_innings, 'rf'))
logging.info(f'all_of: {all_of}')
if len(all_of) > 0:
all_of.sort(key=lambda y: y[1], reverse=True)
logging.info(f'sorted of: {all_of}')
out_string = f'{all_of[0][2]}-{all_of[0][0]}({of_arm})e{of_error}'
if len(all_of) >= 2:
out_string += f', {all_of[1][2]}-{all_of[1][0]}e{of_error}'
if len(all_of) >= 3:
out_string += f', {all_of[2][2]}-{all_of[2][0]}e{of_error}'
logging.info(f'of string: {out_string}')
all_def.append((out_string, of_innings))
all_def.sort(key=lambda z: z[1], reverse=True)
final_defense = ''
for x in all_def:
if len(final_defense) > 0:
final_defense += f', '
final_defense += f'{x[0]}'
return final_defense
def ordered_positions(all_pos: list) -> list:
if len(all_pos) == 0:
return ['DH']
all_def = []
for x in all_pos:
if x.position not in ['OF', 'P', 'SP', 'RP', 'CP']:
all_def.append((x.innings, x.position))
all_def.sort(key=lambda y: y[0], reverse=True)
return [x[1] for x in all_def]
def ordered_pitching_positions(all_pos: list) -> list:
all_def = []
for x in all_pos:
if x.position in ['SP', 'RP', 'CP']:
all_def.append((x.innings, x.position))
all_def.sort(key=lambda y: y[0], reverse=True)
return [x[1] for x in all_def]
def defense_rg(all_pos: list) -> list:
rg_data = [
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None
]
all_pitcher = True
for line in all_pos:
if 'P' not in line.position:
all_pitcher = False
break
for line in all_pos:
if line.position == 'P' and all_pitcher:
this_pit = PitcherData.get_or_none(PitcherData.player == line.player, PitcherData.cardset == line.cardset)
if this_pit:
rg_data[0] = line.range
rg_data[9] = line.error
rg_data[22] = this_pit.wild_pitch
rg_data[23] = this_pit.balk
elif line.position == 'C':
rg_data[1] = line.range
rg_data[10] = line.error
rg_data[19] = line.arm
rg_data[20] = line.overthrow
rg_data[21] = line.pb
elif line.position == '1B':
rg_data[2] = line.range
rg_data[11] = line.error
elif line.position == '2B':
rg_data[3] = line.range
rg_data[12] = line.error
elif line.position == '3B':
rg_data[4] = line.range
rg_data[13] = line.error
elif line.position == 'SS':
rg_data[5] = line.range
rg_data[14] = line.error
elif line.position == 'LF':
rg_data[6] = line.range
elif line.position == 'CF':
rg_data[7] = line.range
elif line.position == 'RF':
rg_data[8] = line.range
elif line.position == 'OF':
rg_data[15] = line.error
rg_data[16] = line.error
rg_data[17] = line.error
rg_data[18] = line.arm
return rg_data
def sanitize_chance_output(total_chances, min_chances=1.0, rounding=0.05):
# r_val = mround(total_chances) if total_chances >= min_chances else 0
r_val = Decimal(total_chances) if total_chances >= min_chances else Decimal(0)
logging.debug(f'r_val: {r_val}')
return Decimal(float(math.floor(r_val / Decimal(rounding)) * Decimal(rounding))).quantize(Decimal("0.05"))
# return r_val.quantize(Decimal(rounding))