PuLP: Stuck with objective function definition - python

I am a first time user of PuLP and I the last time I did linear programming, Python did not exist.
I can solve this problem with LibreOffice's Solve extension (which does LP)
But I need to do it in code.
I want to optimise a stock picking problem.
We need to pick a certain quantity of screws, say 98.
Screws are packed in packs of 25 and 100. I name those pack sizes '25' and '100'.
The cost of the pick needs to be minimised.
There is a cost to pick each pack, and there is a cost to the excess quantity picked.
The constraint is that the quantity picked >= target_qty
For example, if the cost to each unit of excess was 0.1 and the cost to pick the '25' pack was 1 and the cost to pack the '100' pack is 1.1., the cost of picking is 1 x 100 pack is
(100 - 98) *.1 + 0*1 + 1*1.1
This is cheaper than picking 4*'25' pack.
Assuming that there are dicts pack_cost{} and pack_capacity{} which both have the key pack_name,
e.g. pack_cost = {'25':1,'100':1.1} and therefore list_of_pack_names = ['25','100']
I try this:
lp_prob = pulp.LpProblem('PackSizes', pulp.LpMinimize)
packs_used = pulp.LpVariable.dicts("Packs",list_of_pack_names,lowBound=0,cat="Integer")
pack_cost = [pack_costs[pack_name]*packs_used[pack_name] for pack_name in list_of_pack_names]
excess_cost = cost_per_unit * ( sum([pack_sizes[pack_name]*packs_used[pack_name] for pack_name in list_of_pack_names])- original_qty)
lp_prob += pulp.lpSum(pack_cost) + pulp.lpSum(excess_cost) #objective function
# and constraint: total picked >= needed
lp_prob += pulp.lpSum(sum([pack_sizes[pack_name]*packs_used[pack_name] for pack_name in list_of_pack_names]) >= target_qty)
Results:
print("Status:",pulp.LpStatus[lp_prob.status])
shows Optimal
lp_prob.objective is 10*Packs_10 + 15*Packs_15 + 30*Packs_30 - 16.5
but the solution is 0 of each pack size

You may check your problem with
print(lp_prob)
You do not add any essential constraint that prevents all vars from becoming zero.
Probably, you misprinted in the constraint statement. This constraint makes the problem not trivial (check brackets):
lp_prob += pulp.lpSum(sum([pack_sizes[pack_name]*packs_used[pack_name] for pack_name in list_of_pack_names])) >= target_qty

Related

Where does the problem or bug lie in the logic of this while loop Machine Learning Cubic Root-Finding algorithmic code that calls a function?

Goal: The goal of this program is to find additional accelerators to the convergence process of cubic or n-polynomial root finding methods/algorithms.
Problem: The code can not tabulate one variable from the upper section of while loops for some unknown reason I can't delineate. Variable "tbl_val_6" is not defined despite it being clearly defined above (in the code) in the sixth loop. (See sixth line in "Tabulation" code...)
Tabulation:
z = [('Halley (Lower)','Original',act_rad,tbl_val_1**(10**9),iter_num_1),
('Halley (Upper)','Original',act_rad,tbl_val_2*(10**9),iter_num_3),
('Bisection','Original',act_rad,tbl_val_3*(10**9),iter_num_3),
('Regula Falsi','Original',act_rad,tbl_val_4*(10**9),iter_num_4),
('Secant','Original',act_rad,tbl_val_5*(10**9),iter_num_5),
('Halley (Lower)', 'Version 2',act_rad,tbl_val_6*(10**9),iter_num_6),
('Halley (Upper)','Original',act_rad,tbl_val_7*(10**9),iter_num_7),
('Bisection','Version 2',act_rad,tbl_val_8*(10**9),iter_num_8),
('Regula Falsi','Version 2',act_rad,tbl_val_9*(10**9),iter_num_9),
('Secant','Version 2',act_rad,tbl_val_10*(10**9),iter_num_10),
('Halley (Lower)', 'Version 3', act_rad,tbl_val_11*(10**9),iter_num_11),
('Halley (Upper)','Version 3', act_rad,tbl_val_12*(10**9),iter_num_12),
('Bisection','Version 3',act_rad,tbl_val_13*(10**9),iter_num_13),
('Regula Falsi','Version 3',act_rad,tbl_val_14*(10**9),iter_num_14),
('Secant','Version 3',act_rad,tbl_val_15*(10**9),iter_num_15),]
tbl = tabulate(z, headers=['Numerical Method','Version','Act. Val' 'Approx. Val','#Iterations'],
tablefmt='fancy_grid')
print(tbl)
print("***Where Version 2: Iterative Root and Version 3: Reciprocal Factorial: Convergence
Accelerators")
Associated Error:
NameError Traceback (most recent call last)
<ipython-input-1-657a5a0a1bbf> in <module>
460 ('Regula Falsi','Original',act_rad,tbl_val_4*(10**9),iter_num_4),
461 ('Secant','Original',act_rad,tbl_val_5*(10**9),iter_num_5),
--> 462 ('Halley (Lower)', 'Version 2',act_rad,tbl_val_6*(10**9),iter_num_6),
463 ('Halley (Upper)','Original',act_rad,tbl_val_7*(10**9),iter_num_7),
464 ('Bisection','Version 2',act_rad,tbl_val_8*(10**9),iter_num_8),
NameError: name 'tbl_val_6' is not defined
This is the sixth loop out of fifteen that computes the iteration and approximate cubic root. It can not define "tbl_val_6" because the variable defining it "x_new_low_H_2" can not be defining meaning it is most likely having issues calling the functions outside of its loop structure. Or I think it is, however, why did the others have no issue?:
Sixth Loop (where "tbl_val_6" id defined) and it and all 15's initial conditions:
Ini.Cond.
#Root-Finding Method Initial Conditions
crit_lim_low = 1000
crit_lim_up = 1000
itr = 0
Sixth Loop (out of 15):
# Halley's (Lower)
while crit_lim_low > .0005:
x_new_low_H_2 = guess_low - ((2*f(guess_low)*df(guess_low))/((2*(df(guess_low))**2)-
(f(guess_low)*ddf(guess_low))))
crit_lim_low = abs(act_rad - x_new_low_H_2)
if crit_lim_low <= .0005:
itr += 1
iter_num_6 = itr #iter number for table
itr = 0 #Re-initializion
crit_lim_low = 1000
tbl_val_6 = x_new_low_H_2 #value of approx. root (table)
guess_low = act_rad - 10
break
elif itr >= 1000:
print("Lower Guess Halley's Method Failed(V.2): Check parameters.")
itr = 0
crit_lim_low = 1000
guess_low = act_rad - 10
break
else:
itr += 1
j = x_new_low_H_2**(1/itr)
guess_low = x_new_low_H_2 + j
Functions f(x), df(x), and ddf(x) are functions defined above and OUTSIDE of the loop structure; initial conditions and constraints shown for reproducibility:
Input/Initial Conditions/Functions:
print('User Assumptions/Limitations:')
print('\n')
print('1. Radial Upper Limit must be no more than 100 nanometers')
print('and Radial Lower Limit must be no less than 1 nanometer.')
print('2. Period of diffusion must not exceed 24 hours.')
print('3. Diffusion Rate should be reasonable a number of radial')
print('units (nm) per hour as to not supersede other constraints.***')
print('\n')
print('***Note: r = (dr/dt)t must be between RUL and RLL only!')
print('\n')
ul_rad = (10**(-9))*float(input('What is the radial upper limit (nm)? '))
ll_rad = (10**(-9))*float(input('What is the radial lower limit (nm)? '))
diff_t = (10**(-9))*float(input('What is the period of diffusion (hours)? '))
dr_dt = (10**(-9))*float(input('What is the volume rate of diffusion (nm/hour)? '))
#Formula Set-Up
pi = 3.14159265359
import math
#Prelim Calculation
act_rad = math.sqrt((((4/3)*pi*((ul_rad)**3)) - ((4/3)*pi*(dr_dt)*((diff_t)**3)) + ((4/3)*pi*
((ll_rad)**3)))*(3/(4*pi)))
guess_low = act_rad - 10
guess_up = act_rad + 10
##Functions for Root-Finding Methods
def f(x): #volume
return(((4/3)*pi*((ul_rad)**3)) - ((4/3)*pi*(x**3)) - ((4/3)*pi*(dr_dt)*((diff_t)**3)) +
((4/3)*pi*((ll_rad)**3)))
def df(x):#surface area
return((4*pi*((ul_rad)**2)) - (4*pi*(x**2)) - (4*pi*(dr_dt)*((diff_t)**2)) + (4*pi*
((ll_rad)**2)))
def ddf(x):#mean width times curvature
return(((8*pi*ul_rad) - (8*pi*x) - (8*pi*(dr_dt)*(diff_t)) + (8*pi*ll_rad)))
If this needs a global function for "x" I'm not sure how it would since it should be flagged at the first loop instead of the sixth. Re-initialization of critical values, high and low guesses, and etc. looks fine as well as the "tabulate" function. Can anyone see what I am missing? Please be easy on me as I am a novice in Python if the answer is obvious.
Debugging/Self-Deliberation Notes:
Yesterday and this morning I tried to make sure my re-initialization was fine and triple-checked. I also printed "tbl_val's" and "iter_num's" from various loops and none of the loops are iterating or the values are too small for the radii, it seems. This may be an overall issue with the logic of all 15 separate WHILE loops and I'm not sure what's going on as I'm taking care to re-initialize. I have a hunch its having issues calling the outside functions but it should've had issues at the start in the FIRST loop if that were true.
Note: I tried to minimize the code as much as I could but Tabulate, the sixth WHILE loop, and the functions as well as the inputs and initial conditions are KEY and MANDATORY for that output "tbl_val_6". Also, if it is still unclear as to what I am trying to do or more code needs to be seen, I will edit this immediately and as soon as I can!
Are you sure of the value of crit_lim_low ?
You define tbl_val_6 in an if clause in a while loop with both the loop and the if clause dependent on the value of crit_lim_low.
If crit_lim_low is <= 0.0005 then the while loop wont run at all, and you wont define your variable.

Pulp Integer Programming Constraint ignored

I am trying to solve a LpProblem with only boolean variables and Pulp seems to be ignoring some constraints. To give some context about the problem:
I want to find an optimal solution to the problem schools face when trying to create classroom groups. In this case, students are given a paper to write at most 5 other students and the school guarantees them that they will be together with at least one of those students. To see how I modeled this problem into an integer programming problem please refer to this question.
In that link you will see that my variables are defined as x_ij = 1 if student i will be together with student j, and x_i_j = 0 otherwise. Also, in that link I ask about the constraint that I am having trouble implementing with Pulp: if x_i_j = 1 and x_j_k = 1, then by transitive property, x_i_k = 1. In other words, if student i is with student j, and student j is with student k, then, student i will inherently be together with student k.
My objective is to maximize the sum of all the elements of the matrix obtained when performing a Hadamard product between the input matrix and the variables matrix. In other words, I want to contemplate as many of the student's requests as possible.
I will now provide some code snippets and screen captures that should help visualize the problem:
Inputs (just a sample: the real matrix is 37x37)
Output
As you can see in this last image, x_27 = 1 and x_37 = 1 but x_23 = 0 which doesn't make sense.
Here is how I define my variables
def define_variables():
variables = []
for i in range(AMOUNT_OF_STUDENTS):
row = []
for j in range(AMOUNT_OF_STUDENTS):
row.append(LpVariable(f"x_{i}_{j}", lowBound=0, upBound=1, cat='Integer'))
variables.append(row)
return variables
Here is how I define the transitive constraints
for i in range(len(variables)):
for j in range(i, len(variables)):
if i != j:
problem += variables[i][j] == variables[j][i] # Symmetry
for k in range(j, len(variables)):
if i < j < k < len(variables):
problem += variables[i][j] + variables[j][k] - variables[i][k] <= 1 # Transitive
problem += variables[i][j] + variables[i][k] - variables[j][k] <= 1
problem += variables[j][k] + variables[i][k] - variables[i][j] <= 1
When printing the LpProblem I see the constraint that is apparently not working:
As you can see in the output: x_2_7 = 1 and x_3_7 = 1. Therefore, to satisfy this constraint, x_2_3 should also be 1, but as you can also see in the output, it is 0.
Any ideas about what could be happening? I've been stuck for days and the problem seems to be modeled fine and it worked when I only had 8 students (64 variables). Now that I have 37 students (1369 variables) it seems to be behaving oddly. The solver arrives to a solution but it seems to be ignoring some constraints.
Any help is very much appreciated! Thank you in advance.
The constraint is working correctly. Find below the analysis:
(crossposted from github: https://github.com/coin-or/pulp/issues/377)
import pulp as pl
import pytups as pt
path = 'debugSolution.txt'
# import model
_vars, prob = pl.LpProblem.from_json(path)
# get all variables with non-zero value
vars_value = pt.SuperDict(_vars).vfilter(pl.value)
# export the lp
prob.writeLP('debugSolution.lp')
# the constraint you show in the SO problem is:
# _C3833: - x_2_3 + x_2_7 + x_3_7 <= 1
'x_2_7' in vars_value
# True, so x_2_7 has value 1
'x_3_7' in vars_value
# False, so x_3_7 has value 0
'x_2_3' in vars_value
# False, so x_2_3 has value 0
So -0 + 1 + 0 <= 1 means the constraint is respected. There must be a problem with bringing back the value of x_3_7 somewhere because you think is 1 when in pulp it's 0.
This is called a set partitioning problem and PuLP has an example in their documentation here.
In essence, instead of modeling your variables as indicators of whether student A is in the same class as student B, you'll define a mapping between a set of students and a set of classrooms. You can then apply your student preferences as either constraints or part of a maximization objective.

Thompson Sampling: Adding positive rewards to Negative rewards in Python for Artificial Intelligence

in Chapter 5 of AI Crash Course, the author writes
nSelected = nPosReward + nNegReward
for i in range(d):
print('Machine number ' + str(i + 1) + ' was selected ' + str(nSelected[i]) + ' times')
print('Conclusion: Best machine is machine number ' + str(np.argmax(nSelected) + 1))
Why are the number of negative rewards added to the number of positive rewards? To find the best machine shouldn't we only be concerned about the machine with the most positive rewards? I'm confused as to why we need to add the negative with the positive rewards. Also I understand that this is a simulation where you randomly assign successes and and you pre assign success rates. However in a real life situation, how do you know the success rates of each slot machine ahead of time? And how do you know which machines should be assigned a "1" ? Thank you so much! Here is the full code:
# Importing the libraries
import numpy as np
# Setting conversion rates and the number of samples
conversionRates = [0.15, 0.04, 0.13, 0.11, 0.05]
N = 10000
d = len(conversionRates)
# Creating the dataset
X = np.zeros((N, d))
for i in range(N):
for j in range(d):
if np.random.rand() < conversionRates[j]:
X[i][j] = 1
# Making arrays to count our losses and wins
nPosReward = np.zeros(d)
nNegReward = np.zeros(d)
# Taking our best slot machine through beta distribution and updating its losses and wins
for i in range(N):
selected = 0
maxRandom = 0
for j in range(d):
randomBeta = np.random.beta(nPosReward[j] + 1, nNegReward[j] + 1)
if randomBeta > maxRandom:
maxRandom = randomBeta
selected = j
if X[i][selected] == 1:
nPosReward[selected] += 1
else:
nNegReward[selected] += 1
# Showing which slot machine is considered the best
nSelected = nPosReward + nNegReward
for i in range(d):
print('Machine number ' + str(i + 1) + ' was selected ' + str(nSelected[i]) + ' times')
print('Conclusion: Best machine is machine number ' + str(np.argmax(nSelected) + 1))
With more and more feedback, Thompson Sampling shifts its focus more and more from exploration to exploitation. That is, with large nSelected values across the board (due to the large N), all Beta distributions will be quite concentrated around their mean (nPosReward[i]/nSelected[i]) and for larger iterations, with increasing probability, Thompson Sampling will pick the machine that it thinks is the most rewarding. By looking at a long enough horizon, you are pushing the probability of seeing the best considered machine also being the most often picked machine close to 1.
To sum up, your intuition is correct. The machine that is the most rewarding in expectation (given the observed feedback so far) is the one that has the highest empirical mean. Due to the probabilistic phenomena I just described, if you run the algorithm for long enough, the most frequently picked and the machine with highest expected reward will coincide with probability approaching 1.
About the second part of your question, we don't know the success rates. If we knew them, the optimal algorithm would simply pick the ones with the highest success rate at all times. What we do have in real life is observing outputs from these random processes. For example, when you show an online advertisement, you don't know the probability of them clicking. However, with a gross simplification assuming everybody behaves the same way, by showing it to people and observing whether they click it or not, we learn the success rate on the fly.
Steven,
(I'm writing this almost 7 months after you posted. Hopefully you will reply and share some of your own insights, and the code that you used to gain the insight. I started the AI Crash Course in the end of November 2020, and similarly I was curious about the Thompson sampling in chapter 5. In my case I was interested mostly in the cases when Thompson Sampling doesn't select the best machine. I was curious how often the 'worst machine' was selected. So over the past six weeks I've likely tried over a thousand different variations of code to gain some insight. I've likely made over a thousand coding mistakes and hundred different rabbit holes, in an attempt to "catch" when Thompson doesn't work. As well as understand how the betaRandom function and adding the posRewards and NegRewards works. Likely there are mistakes in the following code, and the overall approach to better insight could be made more graphical, so please be kind.:-)
curlycharcoal provides some insight in his thoughtful answers. Even Hadelin in the same chapter offers the reader a lot of insight. What follows is an attempt at an "iterative", "trap the errors" approach that helped me gain some insight. We can try something like the code below and "compare" the result of posReward+negReward vs posRewardOnly.
Consider the following: First, insert a few lines of code that: accumulates posRewardsOnly. Also, insert some additional parameters to the concluding print statement so you can see the results of both. Also insert the true values of the conversion rates (i.e. the mean of the X values) so you can show the actual conversion rates used. Remove the multiple print statements about the selections for the other machines, just to clean up the output.
Second, create a large loop over most of Hadelin's original code. Then iterate over that loop. Since we inserted the posRewardOnly result into the concluding print you can compare the results of when you add in the negative rewards, vs selecting the best machine with positive rewards only. (You can think of this outer loop as a crude "AI" test environment, wherein you gain insight into which approach is a better performer. )
We even insert an array at each iteration, for which machine is selected correctly that compares the with negativeRewards vs posRewardsOnly and graph that at the end. ( I haven't done this but would be fun to see )
We can also insert an array to keep track of the original betaRandom selections on the inner loop compared to the actual best machine, and see how that selection does a drunkards walk over each time step, and eventually sobers up and selects the best machine if N is large enough usually say a few thousand time steps N>5000.
Also, we can compare if there are times where the best machine isn't selected (this will provide some insight into the error rates, of the Thompson Sampling overall), with five machines, and N=600 it's interesting to see sometimes as much as25% the best machine is not selected, and sometimes, even the worst machine is selected (although rarely).
Also, as curlycharcoal noted, that negative rewards aren't always assigned through every N, for every machine, they're only assigned when the result of the betarandom function returns a maxValue, then that machine is selected to provide "a sample". That said, if you play with the code below you might find out that your posOnlyReward idea may perform better, and converge faster than the Pos+Neg reward... or does it? ;-)
######################################################################
# Try and catch when Thompson fails:
# Also, compare performance of selecting
# based on negRewards+posRewards vs just posRewards
# 03 Jan 2021 JFB
#
#######################################################################
import numpy as np
np.set_printoptions(precision=2)
# Note the following are the target conversion rates.
# Further down pls remember to compare actual rates against selected machine.
# Also, in later versions reorder the rates from low to hi and visa-versa
# to determine if there is some "greedy Thompson" bias
# based on order of best rates.
conversionRates = [0.15, 0.04, 0.13, 0.11, 0.05]# hadelins AI Crash Course
N = 200
# Increasing N improves the result, Hadelin explains this in same chapter
# I've found that 10,000 results in about 1% error
# 2000 in about 20% error give or take when using
# Hadelin's original conversion rates above.
# 100 results results in about 48% error,
# and posRewards + negRewards disagree with posRewardOnly varying percent,
# my initial sampling of this indicates will be tricky to determine which
# performs better over a variety of situations. But Hadelin provides code
# to create "tests" with various input states and policies.
catchLimit = 100
d = len(conversionRates)
wrong = 0.0
pcntWrong = 0.0
selectedWrong = 0.0
posOnlyWrong = 0.0
pcntPosOnlyWrong = 0.0
posOnlyVsActual = 0.0
pcntPosOnlyVsActual = 0.0
nSelectedArgMax = -1
NSelectedArgMaxPosOnly = -1
for ii in range( 1, catchLimit):
################---- Original X generator----##########################
#creating the set of the bandit payouts at each time t.
# Five columns, many rows.
# a 1 value means the the slot machine
# paid out if you selected that machine at this point in time.
# this can be improved upon so we can order
# the best to worst, and visa vs.
#
X = np.zeros((N, d))
for i in range(N):
for j in range(d):
if np.random.rand() < conversionRates[j]:
X[i][j] = 1
Xmean = X.mean(axis=0)
##############-- end of the Original X generator----###################
#make arrays to count rewards from the table of losses and wins.
nPosReward = np.zeros(d)
nNegReward = np.zeros(d)
#Taking our best slot machine through beta distribution
# and updating its losses and wins.
# Taking some of the slot machines through the beta distribution,
# with the goal of
# determining which slot machine is the best.
# because sometimes the best slot machine isn't found.
for i in range(N):
selected = 0
maxRandom = 0
for j in range(d):
randomBeta = np.random.beta(nPosReward[j] + 1,
nNegReward[j] + 1)
if randomBeta > maxRandom:
maxRandom = randomBeta
selected = j
if X[i][selected] == 1:
nPosReward[selected] +=1
else:
nNegReward[selected] +=1
nSelected = nPosReward + nNegReward
nSelectedPosOnly = nPosReward
nSelectedArgMax = np.argmax(nSelected) + 1
nSelectedArgMaxPosOnly = np.argmax(nSelectedPosOnly) + 1
XMeanArgMax = np.argmax(Xmean) + 1 # find the actual true best slot machine
if ( nSelectedArgMax != XMeanArgMax and
XMeanArgMax != nSelectedArgMaxPosOnly):
#for i in range(d):
#print('Machine number ' + str(i+1) + ' was selected ' + str(nSelected[i]) + ' times')
print('Fail: Pos&Neg predct slot ' + str(nSelectedArgMax),
'posOnly predct ' + str(nSelectedArgMaxPosOnly),
'But Xconv rates', Xmean,'actual best=',XMeanArgMax,'<>' )
wrong +=1
elif ( nSelectedArgMax != XMeanArgMax and
XMeanArgMax == nSelectedArgMaxPosOnly):
print('PosOnly==Actual pos&neg ' + str(nSelectedArgMax),
'posOnly predct ' + str(nSelectedArgMaxPosOnly),
'But Xconv rates', Xmean,'actual best=',XMeanArgMax,'*' )
selectedWrong +=1
elif ( nSelectedArgMax == XMeanArgMax and
XMeanArgMax != nSelectedArgMaxPosOnly):
print('PosNeg==Actual predcts ' + str(nSelectedArgMax),
'posOnly predct ' + str(nSelectedArgMaxPosOnly),
'But Xconv rates', Xmean,'actual best=',XMeanArgMax,'***' )
posOnlyWrong +=1
elif ( nSelectedArgMax == nSelectedArgMaxPosOnly and
XMeanArgMax != nSelectedArgMax):
print('PosNeg == PosOnly but != actual ' + str(nSelectedArgMax),
'posOnly predct ' + str(nSelectedArgMaxPosOnly),
'But Xconv rates', Xmean,'actual best=',XMeanArgMax,'<>' )
wrong +=1
pcntWrong = wrong / catchLimit * 100
pcntSelectedWrong = selectedWrong / catchLimit * 100
pcntPosOnlyVsActual = posOnlyWrong / catchLimit * 100
print('Catch Limit =', catchLimit, 'N=', N)
print('<>wrong: pos+neg != Actual, and PosOnly != Actual Failure Rate= %.1f' %pcntWrong, '%')
print('* PosOnly == Actual but Actual != pos+neg Failure rate = %.1f' %pcntSelectedWrong,'%')
print('** pos+Neg == Actual but Actual != PosOnly Failure rate = %.1f' %pcntPosOnlyVsActual, '%')
############# END #################

How to find the ideal price for a product (provides highest profit) by searching a range of prices

def profit():
price = input('Enter a price: ')
demand = 650 - (10 * price)
cost_per_item = 35
firm_profit = (price - cost_per_item) * demand
return firm_profit
How can one search through a range of prices to find the price that provides the highest profit based on this relationship between demand and the product?
It is really a mathematical problem. Your formula for profit is:
profit = (p - c) * d = (p - c) * (650 - 10 * p)
where I abbreviated p for price, c for cost_per_item, d for demand.
To maximize profit all that you need to do is to find the value of p for which derivative of profit with regard to p is zero:
d(profit) / d(p) = 650 + 10*c - 20*p = 0 =>
pmax = (650 + 10*c) / 20
If you need to pick a price from a list of possible values, pick the one closest to pmax (since profit is a parabola with regard to p and so it is symmetric around the vertical line passing through pmax).
Therefore, if you do have a list of prices (I suppose this is what you mean by "range of values") contained in a list prices, then:
best_price = min(abs(x - pmax) for x in prices)
where pmax was computed earlier.
Another possibility: first define a demand function, next a profit function and eventually use the max builtin applied to a generator expression that produces couples of values (profit, price) that max, by default, compares taking into account the first value in the couple.
>>> def demand_01(price):
... return 650-10*price
>>> def profit(price, cost, demand):
... return (price-cost)*demand(price)
>>> print(max((profit(price, 35, demand_01), price) for price in (39.95, 69.90, 99.00))
(1239.9750000000008, 39.95)
The advantage of definining separately a demand and a profit function and using max lies in
the possibility of using whatever mathematical definition (even piecewise), w/o involving the differentiability of the profitfunction and
the possibility of definining different demand functions to explore the sensitivity of the results on different assumptions.
Addendum
To have the best price and the associated maximum profit you can unpack the result returned by the max builtin:
max_profit, best_price = max( (profit(price, 35, demand_01), price)
for price in (...))
If this problem uses discreet units (for example, if you must use integers) then you just use a loop to try every possibility from a price of 0 to a price which would produce a zero demand (65 in this case, because 650 - 10*65 = 0).
I would start by moving the price from an input to a parameter of the function, so:
def profit(price):
demand = 650 - (10 * price)
cost_per_item = 35
firm_profit = (price - cost_per_item) * demand
return firm_profit
Then we define a variable to pass through the function and increment it to try every possibility:
price = 0
best_price = 0
increment = 1
while price<65:
if profit(price)>profit(best_price):
best_price = price
price += increment
If you end up needing to use a decimal increment, you might want to use the decimal module to avoid some strange, floating point behavior.

How to Backtest a Strategy in Python

I need to be able to determine whether a particular "trade" (indicated by "signal") resulted in a profit or loss by indicating a win or loss for each.
I need Python to check the next location ( the signal or entry point or date + 1 ) in the High and Low lists ( the lists: close, highs, and lows will have the same number of values ) for an increase in value equal to or greater than 2.5% at some point beyond the entry signal.
However, I also want Python to determine if the value drops 3% or more prior to appreciating 2.5% or more.
This must occur for each entry in signal.
In essence, I need a limit to sell at 102.5% and a stop at 97%.
Unfortunately, the code I developed so far doesn't seem to be working.
What am I missing?
signals = [1,5,7]
close = [5,10,10,10.5,11,12,11.9,14,14,15,16]
highs = [7,10.2,10.1,11,12,12.1,12.2,14.5,18,19,20]
lows = [4,9.9,9.8,10,10,11.8,11.8,12,13.8,13.85,14]
for i in signals:
entry = close[i]
print i
for high in highs[i+1:]:
profit = ( ( high - entry ) / entry ) * 100
for low in lows[i+1:]:
loss = ( ( low - entry ) / entry ) * 100
if abs( loss ) < 3:
if profit >= 2.5:
print 'Win'
else:
print 'Loss'
Your profit is only calculated for highs[-1] while loss is only calculated for lows[-1]. Everything else is discarded, as you replace profit and loss in each loop.
You want to find the set of values where your condition is true. Use zip to put lows and highs together:
for i in signals:
entry = float(close[i])
for high, low in zip(high[i + 1:], low[i + 1:]):
profit = ((high - entry) / entry) * 100
loss = ((low - entry) / entry) * 100
if loss > -3:
if profit >= 2.5:
print "Win"
else:
print "Loss"
Did you already check python-libraries for backtesting? In fact I use other libraries, but there are some very popular python-based solutions such as "pybacktest", "PyAlgoTrade", or "UltraFinance". Maybe integrating such a library could be advantageous for your use case...

Categories

Resources